mirror of
https://github.com/Alfresco/SearchServices.git
synced 2026-09-16 18:12:56 +00:00
Merge master into V2.0.x.
This commit is contained in:
@@ -40,4 +40,4 @@ More details are available at [search-services](/search-services) folder.
|
||||
|
||||
**Following resources will not be available for Community users**
|
||||
|
||||
More details are available at [insight-engine](/insight-engine) folder.
|
||||
More details are available at [insight-engine](/insight-engine) folder.
|
||||
@@ -60,10 +60,14 @@ module.exports = class extends Generator {
|
||||
},
|
||||
{
|
||||
whenFunction: response => response.httpMode == 'http',
|
||||
type: 'confirm',
|
||||
type: 'list',
|
||||
name: 'replication',
|
||||
message: 'Would you like to use SOLR Replication (2 nodes in master-slave)?',
|
||||
default: false
|
||||
message: 'Would you like to use SOLR Replication?',
|
||||
choices: [
|
||||
{ name: "No", value: "" },
|
||||
{ name: "Yes - two nodes in a master-slave configuration", value: "master-slave" },
|
||||
{ name: "Yes - two nodes in a master-master configuration", value: "master-master" }
|
||||
]
|
||||
},
|
||||
// Enterprise only options
|
||||
{
|
||||
@@ -199,7 +203,7 @@ module.exports = class extends Generator {
|
||||
port: (this.props.httpWebMode == 'http' ? '8080' : '443'),
|
||||
secureComms: (this.props.httpMode == 'http' ? 'none' : 'https'),
|
||||
alfrescoPort: (this.props.httpMode == 'http' ? '8080' : '8443'),
|
||||
replication: (this.props.replication ? "true" : "false"),
|
||||
replication: this.props.replication,
|
||||
searchSolrHost: (this.props.replication ? "solr6secondary" : "solr6"),
|
||||
searchPath: searchBasePath,
|
||||
zeppelin: (this.props.zeppelin ? "true" : "false"),
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false" <% } %>
|
||||
mem_limit: 1200m
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
volumes:
|
||||
- ./keystores/solr:/opt/<%=searchPath%>/keystore <% } %>
|
||||
|
||||
<% if (replication == 'true') { %>
|
||||
<% if (replication) { %>
|
||||
solr6secondary:
|
||||
build:
|
||||
context: ./search
|
||||
@@ -88,9 +88,9 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false"
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: <% if (replication == 'master-master') { %>"true"<% } else { %>"false"<% } %>
|
||||
ENABLE_SLAVE: <% if (replication == 'master-master') { %>"false"<% } else { %>"true"<% } %>
|
||||
MASTER_HOST: solr6 <% } %>
|
||||
mem_limit: 1200m
|
||||
environment:
|
||||
|
||||
+5
-5
@@ -57,7 +57,7 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false" <% } %> <% if (sharding == 'true') { %>
|
||||
ENABLE_SHARDING: "true"
|
||||
@@ -103,7 +103,7 @@ services:
|
||||
volumes:
|
||||
- ./keystores/solr:/opt/<%=searchPath%>/keystore <% } %>
|
||||
|
||||
<% if (sharding == 'true' || replication == 'true') { %>
|
||||
<% if (sharding == 'true' || replication) { %>
|
||||
solr6secondary:
|
||||
build:
|
||||
context: ./search
|
||||
@@ -113,9 +113,9 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false"
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: <% if (replication == 'master-master') { %>"true"<% } else { %>"false"<% } %>
|
||||
ENABLE_SLAVE: <% if (replication == 'master-master') { %>"false"<% } else { %>"true"<% } %>
|
||||
MASTER_HOST: solr6 <% } %> <% if (sharding == 'true') { %>
|
||||
ENABLE_SHARDING: "true"
|
||||
NUM_SHARDS: "2"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
ALFRESCO_TAG=latest
|
||||
SHARE_TAG=6.1.0-RC3
|
||||
POSTGRES_TAG=10.9
|
||||
TRANSFORM_ROUTER_TAG=1.1.0-EA2
|
||||
PDF_RENDERER_TAG=2.1.0-EA4
|
||||
IMAGE_MAGICK_TAG=2.1.0-EA4
|
||||
LIBREOFFICE_TAG=2.1.0-EA4
|
||||
TIKA_TAG=2.1.0-EA4
|
||||
TRANSFORM_MISC_TAG=2.1.0-EA4
|
||||
SHARE_TAG=6.2.0
|
||||
POSTGRES_TAG=11.4
|
||||
TRANSFORM_ROUTER_TAG=1.1.0
|
||||
PDF_RENDERER_TAG=2.1.0
|
||||
IMAGE_MAGICK_TAG=2.1.0
|
||||
LIBREOFFICE_TAG=2.1.0
|
||||
TIKA_TAG=2.1.0
|
||||
TRANSFORM_MISC_TAG=2.1.0
|
||||
SHARED_FILE_STORE_TAG=0.5.3
|
||||
ACTIVE_MQ_TAG=5.15.8
|
||||
DIGITAL_WORKSPACE_TAG=1.1.0
|
||||
DIGITAL_WORKSPACE_TAG=1.3.0
|
||||
ACS_NGINX_TAG=3.0.1
|
||||
ACS_COMMUNITY_NGINX_TAG=1.0.0
|
||||
SEARCH_TAG=latest
|
||||
|
||||
+5
-5
@@ -63,7 +63,7 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false" <% } %>
|
||||
mem_limit: 1200m
|
||||
@@ -94,7 +94,7 @@ services:
|
||||
volumes:
|
||||
- ./keystores/solr:/opt/<%=searchPath%>/keystore <% } %>
|
||||
|
||||
<% if (replication == 'true') { %>
|
||||
<% if (replication) { %>
|
||||
solr6secondary:
|
||||
build:
|
||||
context: ./search
|
||||
@@ -104,9 +104,9 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false"
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: <% if (replication == 'master-master') { %>"true"<% } else { %>"false"<% } %>
|
||||
ENABLE_SLAVE: <% if (replication == 'master-master') { %>"false"<% } else { %>"true"<% } %>
|
||||
MASTER_HOST: solr6 <% } %>
|
||||
mem_limit: 1200m
|
||||
environment:
|
||||
|
||||
+5
-5
@@ -66,7 +66,7 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false" <% } %> <% if (sharding == 'true') { %>
|
||||
ENABLE_SHARDING: "true"
|
||||
@@ -112,7 +112,7 @@ services:
|
||||
volumes:
|
||||
- ./keystores/solr:/opt/<%=searchPath%>/keystore <% } %>
|
||||
|
||||
<% if (sharding == 'true' || replication == 'true') { %>
|
||||
<% if (sharding == 'true' || replication) { %>
|
||||
solr6secondary:
|
||||
build:
|
||||
context: ./search
|
||||
@@ -122,9 +122,9 @@ services:
|
||||
ALFRESCO_HOSTNAME: alfresco
|
||||
ALFRESCO_COMMS: <%=secureComms%> <% if (httpMode == 'https') { %>
|
||||
TRUSTSTORE_TYPE: JCEKS
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication == 'true') { %>
|
||||
ENABLE_MASTER: "true"
|
||||
ENABLE_SLAVE: "false"
|
||||
KEYSTORE_TYPE: JCEKS <% } %> <% if (replication) { %>
|
||||
ENABLE_MASTER: <% if (replication == 'master-master') { %>"true"<% } else { %>"false"<% } %>
|
||||
ENABLE_SLAVE: <% if (replication == 'master-master') { %>"false"<% } else { %>"true"<% } %>
|
||||
MASTER_HOST: solr6 <% } %> <% if (sharding == 'true') { %>
|
||||
ENABLE_SHARDING: "true"
|
||||
NUM_SHARDS: "2"
|
||||
|
||||
+15
-5
@@ -11,9 +11,9 @@
|
||||
<name>Search Analytics E2E Tests</name>
|
||||
<description>Test Project to test Search Service and Analytics Features on a complete setup of Alfresco, Share</description>
|
||||
<properties>
|
||||
<tas.rest.api.version>6.0.1.2</tas.rest.api.version>
|
||||
<tas.cmis.api.version>6.0.0.4</tas.cmis.api.version>
|
||||
<tas.utility.version>3.0.14</tas.utility.version>
|
||||
<tas.rest.api.version>1.26</tas.rest.api.version>
|
||||
<tas.cmis.api.version>1.12</tas.cmis.api.version>
|
||||
<tas.utility.version>3.0.17</tas.utility.version>
|
||||
<rm.version>3.2.0</rm.version>
|
||||
<suiteXmlFile>src/test/resources/SearchSuite.xml</suiteXmlFile>
|
||||
<test.exclude></test.exclude>
|
||||
@@ -44,7 +44,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi-test</artifactId>
|
||||
<artifactId>restapi</artifactId>
|
||||
<version>${tas.rest.api.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
@@ -56,7 +56,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>cmis-test</artifactId>
|
||||
<artifactId>cmis</artifactId>
|
||||
<version>${tas.cmis.api.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
@@ -71,6 +71,12 @@
|
||||
<artifactId>alfresco-governance-services-automation-enterprise-rest-api</artifactId>
|
||||
<version>${rm.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi-test</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -88,6 +94,10 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi-test</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- alfresco tester settings -->
|
||||
|
||||
@@ -38,6 +38,7 @@ public class TestGroup
|
||||
public static final String ACS_61n = "ACS_61n"; // Alfresco Content Services 6.1 or above
|
||||
public static final String ACS_611n = "ACS_611n"; // Alfresco Content Services 6.1.1 or above
|
||||
public static final String ACS_62n = "ACS_62n"; // Alfresco Content Services 6.2 or above
|
||||
public static final String ACS_63n = "ACS_63n"; // Alfresco Content Services 6.3 or above
|
||||
|
||||
public static final String AGS_302 = "AGS_302"; // Alfresco governance Services 3.0.2 or above
|
||||
|
||||
|
||||
+179
-54
@@ -6,11 +6,22 @@
|
||||
*/
|
||||
package org.alfresco.test.search.functional;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import static lombok.AccessLevel.PROTECTED;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.alfresco.cmis.CmisWrapper;
|
||||
import org.alfresco.dataprep.ContentService;
|
||||
import org.alfresco.dataprep.SiteService.Visibility;
|
||||
import org.alfresco.rest.core.RestProperties;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestRequestSpellcheckModel;
|
||||
import org.alfresco.rest.search.RestRequestHighlightModel;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
@@ -37,15 +48,10 @@ import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.BeforeSuite;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static lombok.AccessLevel.PROTECTED;
|
||||
|
||||
/**
|
||||
* @author meenal bhave
|
||||
*/
|
||||
@ContextConfiguration("classpath:alfresco-search-e2e-context.xml")
|
||||
@ContextConfiguration ("classpath:alfresco-search-e2e-context.xml")
|
||||
public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
/** The number of retries that a query will be tried before giving up. */
|
||||
@@ -75,27 +81,26 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
protected CmisWrapper cmisApi;
|
||||
|
||||
@Autowired
|
||||
@Getter(value = PROTECTED)
|
||||
// @Getter(value = PROTECTED)
|
||||
protected DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
@Getter(value = PROTECTED)
|
||||
@Getter (value = PROTECTED)
|
||||
private ContentService contentService;
|
||||
|
||||
protected UserModel testUser, adminUserModel;
|
||||
protected SiteModel testSite;
|
||||
protected UserModel testUser, adminUserModel, testUser2;
|
||||
protected SiteModel testSite, testSite2;
|
||||
|
||||
protected static String unique_searchString;
|
||||
|
||||
|
||||
protected static final String SEARCH_LANGUAGE_CMIS = "cmis";
|
||||
|
||||
|
||||
protected enum SearchLanguage {
|
||||
CMIS,
|
||||
AFTS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@BeforeSuite(alwaysRun = true)
|
||||
@BeforeSuite (alwaysRun = true)
|
||||
public void beforeSuite() throws Exception
|
||||
{
|
||||
super.springTestContextPrepareTestInstance();
|
||||
@@ -104,7 +109,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
deployCustomModel("model/finance-model.xml");
|
||||
}
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@BeforeClass (alwaysRun = true)
|
||||
public void setup()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
@@ -209,7 +214,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
customModel.setNodeRef(modelInRepo.getId());
|
||||
customModel.setNodeRef(customModel.getNodeRefWithoutVersion());
|
||||
customModel.setCmisLocation(String.format("/Data Dictionary/Models/%s", fileName));
|
||||
LOGGER.info("Custom Model file: " + customModel.getCmisLocation());
|
||||
LOGGER.info("Custom Model file: " + customModel.getCmisLocation());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -239,9 +244,9 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
/**
|
||||
* Wait for Solr to finish indexing and search to return appropriate results
|
||||
*
|
||||
* @param userQuery Search Query
|
||||
* @param contentToFind that's expected to be included / excluded from the results
|
||||
*
|
||||
* @param userQuery Search Query
|
||||
* @param contentToFind that's expected to be included / excluded from the results
|
||||
* @param expectedInResults Whether we expect the content in the results or not.
|
||||
* @return true if search returns expected results, i.e. is given content is found or excluded from the results
|
||||
*/
|
||||
@@ -259,10 +264,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
if (restClient.getStatusCode().matches(expectedStatusCode))
|
||||
{
|
||||
boolean found = response.getEntries().stream()
|
||||
.map(entry -> entry.getModel().getName())
|
||||
.filter(name -> name.equalsIgnoreCase(contentName) || contentName.isBlank())
|
||||
.count() > 0;
|
||||
boolean found = isContentInSearchResponse(response, contentName);
|
||||
|
||||
// Exit loop if result is as expected.
|
||||
if (expectedInResults == found)
|
||||
@@ -281,10 +283,24 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if the contentName is returned in the SearchResponse.
|
||||
*
|
||||
* @param response the search response
|
||||
* @param contentName the text we are using as matching/verifying criteria.
|
||||
* @return true if if the item with the contentName text is returned in the SearchResponse.
|
||||
*/
|
||||
public boolean isContentInSearchResponse(SearchResponse response, String contentName)
|
||||
{
|
||||
return response.getEntries().stream()
|
||||
.map(entry -> entry.getModel().getName())
|
||||
.anyMatch(name -> name.equalsIgnoreCase(contentName) || contentName.isBlank());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Solr to finish indexing: Indexing has caught up = true if search returns appropriate results
|
||||
*
|
||||
* @param userQuery: search query, this can include the fieldname, unique search string will guarantee accurate results
|
||||
*
|
||||
* @param userQuery: search query, this can include the fieldname, unique search string will guarantee accurate results
|
||||
* @param expectedInResults, true if entry is expected in the results set
|
||||
* @return true (indexing is finished) if search returns appropriate results
|
||||
*/
|
||||
@@ -296,7 +312,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
/**
|
||||
* waitForIndexing method that matches / waits for filename, metadata to be indexed.
|
||||
*
|
||||
*
|
||||
* @param userQuery
|
||||
* @param expectedInResults
|
||||
* @return
|
||||
@@ -309,7 +325,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
/**
|
||||
* waitForIndexing method that matches / waits for content to be indexed, this can take longer than metadata indexing.
|
||||
* Since Metadata is indexed first, use this method where tests, queries need content to be indexed too.
|
||||
*
|
||||
*
|
||||
* @param userQuery
|
||||
* @param expectedInResults
|
||||
* @return
|
||||
@@ -321,9 +337,9 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
/**
|
||||
* Wait for Solr to finish indexing: Indexing has caught up = true if search returns appropriate results
|
||||
*
|
||||
* @param fieldName: specific field to search for, e.g. name. When specified, the query will become: name:'userQuery'
|
||||
* @param userQuery: search string, unique search string will guarantee accurate results
|
||||
*
|
||||
* @param fieldName: specific field to search for, e.g. name. When specified, the query will become: name:'userQuery'
|
||||
* @param userQuery: search string, unique search string will guarantee accurate results
|
||||
* @param expectedInResults, true if entry is expected in the results set
|
||||
* @return true (indexing is finished) if search returns appropriate results
|
||||
*/
|
||||
@@ -336,7 +352,7 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
/**
|
||||
* Run a search as admin user and return the response
|
||||
*
|
||||
*
|
||||
* @param queryString: string to search for, unique search string will guarantee accurate results
|
||||
* @return the search response from the API
|
||||
*/
|
||||
@@ -347,8 +363,8 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
/**
|
||||
* Run a search as given user and return the response
|
||||
*
|
||||
* @param user: UserModel for the user you wish to run the query as
|
||||
*
|
||||
* @param user: UserModel for the user you wish to run the query as
|
||||
* @param queryString: string to search for, unique search string will guarantee accurate results
|
||||
* @return the search response from the API
|
||||
*/
|
||||
@@ -360,14 +376,23 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
searchRequest.setQuery(queryModel);
|
||||
return restClient.authenticateUser(user).withSearchAPI().search(searchRequest);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run a search as given user and return the response
|
||||
*
|
||||
* @param user: UserModel for the user you wish to run the query as
|
||||
* @param queryModel: The queryModel to search for, containing the query
|
||||
* Run a search with Spellcheck as given user and return the response
|
||||
* @param user UserModel for the user you wish to run the query as
|
||||
* @param queryModel The queryModel to search for, containing the query
|
||||
* @param spellcheckQuery The Spellcheck Model containing the query
|
||||
* @return the search response from the API
|
||||
*/
|
||||
protected SearchResponse queryAsUser(UserModel user, RestRequestQueryModel queryModel, RestRequestSpellcheckModel spellcheckQuery)
|
||||
{
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
searchRequest.setQuery(queryModel);
|
||||
searchRequest.setSpellcheck(spellcheckQuery);
|
||||
|
||||
return restClient.authenticateUser(user).withSearchAPI().search(searchRequest);
|
||||
}
|
||||
|
||||
protected SearchResponse queryAsUser(UserModel user, RestRequestQueryModel queryModel)
|
||||
{
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
@@ -396,27 +421,22 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
query.setQuery(queryReq);
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
protected DataUser getDataUser()
|
||||
{
|
||||
return dataUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test if the search query works and count matches where provided
|
||||
* @param query: AFTS or cmis query string
|
||||
* @param expectedCount: Only successful response is checked, when expectedCount is null (can not be exactly specified),
|
||||
* @param setCmis: Query language is set to cmis when setCmis is true, AFTS when false
|
||||
*
|
||||
* @param query: AFTS or cmis query string
|
||||
* @param expectedCount: Only successful response is checked, when expectedCount is null (can not be exactly specified),
|
||||
* @return SearchResponse
|
||||
*/
|
||||
protected SearchResponse testSearchQuery(String query, Integer expectedCount, SearchLanguage queryLanguage)
|
||||
{
|
||||
RestRequestQueryModel queryModel = new RestRequestQueryModel();
|
||||
queryModel.setQuery(query);
|
||||
|
||||
if (ofNullable(queryLanguage).isPresent())
|
||||
{
|
||||
queryModel.setLanguage(queryLanguage.toString());
|
||||
}
|
||||
|
||||
SearchResponse response = queryAsUser(testUser, queryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
SearchResponse response = performSearch(testUser, query, queryLanguage);
|
||||
|
||||
if (ofNullable(expectedCount).isPresent())
|
||||
{
|
||||
@@ -425,4 +445,109 @@ public abstract class AbstractE2EFunctionalTest extends AbstractTestNGSpringCont
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test if the search query returns the expected results in the order given.
|
||||
*
|
||||
* @param query: AFTS or cmis query string
|
||||
* @param expectedNames: The ordered list of names expected to be returned,
|
||||
* @return SearchResponse
|
||||
*/
|
||||
protected SearchResponse testSearchQueryOrdered(String query, List<String> expectedNames, SearchLanguage queryLanguage)
|
||||
{
|
||||
SearchResponse response = performSearch(testUser, query, queryLanguage);
|
||||
|
||||
List<String> names = response.getEntries().stream().map(s -> s.getModel().getName()).collect(Collectors.toList());
|
||||
|
||||
// Include lists in failure message as TestNG won't do this for lists.
|
||||
assertEquals(names, expectedNames, "Unexpected results for query: " + query + " Expected: " + expectedNames + " but got " + names);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test if the search query returns the expected set of results.
|
||||
*
|
||||
* @param query: AFTS or cmis query string
|
||||
* @param expectedNames: The ordered list of names expected to be returned,
|
||||
* @return SearchResponse
|
||||
*/
|
||||
protected SearchResponse testSearchQueryUnordered(String query, Set<String> expectedNames, SearchLanguage queryLanguage)
|
||||
{
|
||||
SearchResponse response = performSearch(testUser, query, queryLanguage);
|
||||
|
||||
Set<String> names = response.getEntries().stream().map(s -> s.getModel().getName()).collect(Collectors.toSet());
|
||||
|
||||
assertEquals(names, expectedNames, "Unexpected results for query: " + query);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private SearchResponse performSearch(UserModel asUser, String query, SearchLanguage queryLanguage)
|
||||
{
|
||||
RestRequestQueryModel queryModel = new RestRequestQueryModel();
|
||||
queryModel.setQuery(query);
|
||||
|
||||
if (!ofNullable(asUser).isPresent())
|
||||
{
|
||||
asUser = testUser;
|
||||
}
|
||||
|
||||
if (ofNullable(queryLanguage).isPresent())
|
||||
{
|
||||
queryModel.setLanguage(queryLanguage.toString());
|
||||
}
|
||||
|
||||
SearchResponse response = queryAsUser(asUser, queryModel);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create and run a simple spellcheck query
|
||||
* When a spellcheck query is run a user, the query inputed and the user query is inputted
|
||||
* @param query
|
||||
* @param userQuery
|
||||
* @return
|
||||
*/
|
||||
protected SearchResponse SearchSpellcheckQuery(UserModel user, String query, String userQuery)
|
||||
{
|
||||
RestRequestSpellcheckModel spellCheck = new RestRequestSpellcheckModel();
|
||||
spellCheck.setQuery(userQuery);
|
||||
|
||||
UserModel searchUser = ofNullable(user).isPresent() ? user : testUser;
|
||||
SearchRequest searchReq = new SearchRequest();
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery(query);
|
||||
queryReq.setUserQuery(userQuery);
|
||||
searchReq.setQuery(queryReq);
|
||||
searchReq.setSpellcheck(spellCheck);
|
||||
SearchResponse response = queryAsUser(searchUser, queryReq, spellCheck);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check the spellcheck object returned in the Search Response
|
||||
* @param response SearchResponse
|
||||
* @param spellCheckType String Values: searchInsteadFor, didYouMean or null
|
||||
* @param spellCheckSuggestion String Values: suggestion string or null
|
||||
*/
|
||||
public void testSearchSpellcheckResponse(SearchResponse response, String spellCheckType, String spellCheckSuggestion)
|
||||
{
|
||||
if (ofNullable(spellCheckType).isPresent())
|
||||
{
|
||||
response.getContext().assertThat().field("spellCheck").isNotEmpty();
|
||||
response.getContext().getSpellCheck().assertThat().field("type").is(spellCheckType);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.getContext().assertThat().field("spellCheck").isNull();
|
||||
}
|
||||
|
||||
if (ofNullable(spellCheckSuggestion).isPresent())
|
||||
{
|
||||
response.getContext().assertThat().field("spellCheck").isNotEmpty();
|
||||
response.getContext().getSpellCheck().assertThat().field("suggestions").contains(spellCheckSuggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+421
-22
@@ -1,5 +1,8 @@
|
||||
package org.alfresco.test.search.functional.searchServices.cmis;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.data.CustomObjectTypeProperties;
|
||||
import org.alfresco.utility.data.provider.XMLDataConfig;
|
||||
@@ -15,7 +18,7 @@ public class SolrSearchByPropertyTests extends AbstractCmisE2ETest
|
||||
private FolderModel guestf, tesf, restf, testtttf, testf, testf1, testf2, testf3, testf4;
|
||||
private FileModel guestc, restc, tesc, testtttc, testc, testc1, testc2, testc3;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@BeforeClass (alwaysRun = true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
dataContent.usingAdmin().deployContentModel("model/tas-model.xml");
|
||||
@@ -46,68 +49,464 @@ public class SolrSearchByPropertyTests extends AbstractCmisE2ETest
|
||||
// Sites
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createCustomContent(guestf, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "guestf text")
|
||||
.addProperty("tas:IntPropertyF", 222));
|
||||
.addProperty("tas:IntPropertyF", 222));
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createCustomContent(tesf, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "tesf text")
|
||||
.addProperty("tas:IntPropertyF", 224));
|
||||
.addProperty("tas:IntPropertyF", 224));
|
||||
|
||||
// Sites >> Folders
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(restf, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "restf text")
|
||||
.addProperty("tas:IntPropertyF", 223));
|
||||
.addProperty("tas:IntPropertyF", 223));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testtttf, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testtttf text")
|
||||
.addProperty("tas:IntPropertyF", 225));
|
||||
.addProperty("tas:IntPropertyF", 225));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testf, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testf text")
|
||||
.addProperty("tas:IntPropertyF", 226));
|
||||
.addProperty("tas:IntPropertyF", 226));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testf1, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testf1 text")
|
||||
.addProperty("tas:IntPropertyF", 2221));
|
||||
.addProperty("tas:IntPropertyF", 2221));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testf2, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testf2 text")
|
||||
.addProperty("tas:IntPropertyF", 2222));
|
||||
.addProperty("tas:IntPropertyF", 2222));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testf3, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testf3 text")
|
||||
.addProperty("tas:IntPropertyF", 2223));
|
||||
.addProperty("tas:IntPropertyF", 2223));
|
||||
dataContent.usingUser(testUser).usingResource(guestf).createCustomContent(testf4, "F:tas:folder",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyF", "testf4 text")
|
||||
.addProperty("tas:IntPropertyF", 2224));
|
||||
.addProperty("tas:IntPropertyF", 2224));
|
||||
|
||||
// Sites >> Files
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(guestc, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "guestc text")
|
||||
.addProperty("tas:IntPropertyC", 222));
|
||||
.addProperty("tas:IntPropertyC", 222));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(restc, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "restc text")
|
||||
.addProperty("tas:IntPropertyC", 223));
|
||||
.addProperty("tas:IntPropertyC", 223));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(tesc, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "tesc text")
|
||||
.addProperty("tas:IntPropertyC", 224));
|
||||
.addProperty("tas:IntPropertyC", 224));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(testtttc, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "testtttc text")
|
||||
.addProperty("tas:IntPropertyC", 225));
|
||||
.addProperty("tas:IntPropertyC", 225));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(testc, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "testc text")
|
||||
.addProperty("tas:IntPropertyC", 226));
|
||||
.addProperty("tas:IntPropertyC", 226));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(testc1, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "testc1 text")
|
||||
.addProperty("tas:IntPropertyC", 2221));
|
||||
.addProperty("tas:IntPropertyC", 2221));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(testc2, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "testc2 text")
|
||||
.addProperty("tas:IntPropertyC", 2222));
|
||||
.addProperty("tas:IntPropertyC", 2222));
|
||||
dataContent.usingUser(testUser).usingResource(tesf).createCustomContent(testc3, "D:tas:document",
|
||||
new CustomObjectTypeProperties().addProperty("tas:TextPropertyC", "testc3 text")
|
||||
.addProperty("tas:IntPropertyC", 2223));
|
||||
.addProperty("tas:IntPropertyC", 2223));
|
||||
|
||||
// wait for solr index
|
||||
Utility.waitToLoopTime(getSolrWaitTimeInSeconds());
|
||||
}
|
||||
|
||||
@Test(dataProviderClass = XMLTestDataProvider.class, dataProvider = "getQueriesData")
|
||||
@XMLDataConfig(file = "src/test/resources/testdata/search-by-property.xml")
|
||||
public void executeSearchByProperty(QueryModel query) throws Exception
|
||||
@Test
|
||||
public void testFileNameEquality()
|
||||
{
|
||||
cmisApi.authenticateUser(testUser).withQuery(query.getValue()).assertResultsCount().equals(query.getResults());
|
||||
String query = "SELECT * FROM tas:document where cmis:name = 'testc.txt'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name <> 'testc.txt'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "restc.txt", "tesc.txt", "testtttc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name IN('testc.txt', 'guestc.txt', 'restc.txt')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt", "guestc.txt", "restc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameNotIn()
|
||||
{
|
||||
// Nb. "gustc" is missing an "e".
|
||||
String query = "SELECT * FROM tas:document where cmis:name NOT IN('testc.txt', 'gustc.txt', 'restc.txt')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "tesc.txt", "testtttc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameLike()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE '%testc%'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameLikeExact()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE 'testc.txt'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNamePrefixSuffix()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE 't%tc.txt'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt", "testtttc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameUnderscore()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE 't__tc.txt'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameEquality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name = 'testf'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name <> 'testf'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "restf", "testtttf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name IN('testf', 'guestf', 'restf')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf", "guestf", "restf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameNotIn()
|
||||
{
|
||||
// Nb. "gustc" is missing an "e".
|
||||
String query = "SELECT * FROM tas:folder where cmis:name NOT IN('testf', 'gustf', 'restf')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "testtttf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameLike()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE '%testf%'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameLikeExact()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE 'testf'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNamePrefixSuffix()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE 't%tf'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testtttf", "testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameUnderscore()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE 't__tf'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameOrderAsc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name ASC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testc1.txt", "testc2.txt", "testc3.txt", "testc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameOrderDesc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name DESC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testc.txt", "testc3.txt", "testc2.txt", "testc1.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileOrderNameDescDateAsc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name DESC, cmis:creationDate ASC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testc.txt", "testc3.txt", "testc2.txt", "testc1.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameOrderAsc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name ASC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderNameOrderDesc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name DESC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testf4", "testf3", "testf2", "testf1", "testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderOrderNameDescDateAsc()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name DESC, cmis:creationDate ASC";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningOrderedValues("cmis:name",
|
||||
List.of("testf4", "testf3", "testf2", "testf1", "testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyEquality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC = 'restc text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC <> 'testc1 text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "restc.txt", "tesc.txt", "testtttc.txt", "testc.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC IN('restc text', 'testc2 text', 'testc3 text')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restc.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyNotIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC NOT IN('restc text', 'testc2 text', 'testc3 text')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "tesc.txt", "testtttc.txt", "testc.txt", "testc1.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyLike()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC LIKE '%restc%'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileCustomPropertyUnderscore()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:TextPropertyC LIKE 't__tc text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyEquality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF = 'restf text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF <> 'testf1 text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "restf", "testtttf", "testf", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF IN('restf text', 'testf2 text', 'testf3 text')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restf", "testf2", "testf3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyNotIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF NOT IN('restf text', 'testf2 text', 'testf3 text')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "testtttf", "testf", "testf1", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyLike()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF LIKE '%restf%'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("restf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderCustomPropertyUnderscore()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:TextPropertyF LIKE 't__tf text'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntEquality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC = '222'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC <> '223'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "tesc.txt", "testtttc.txt", "testc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntLessThen()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC < '223'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntLessThanOrEqual()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC <= '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "restc.txt", "tesc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntGreaterThanOrEqual()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC >= '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("tesc.txt", "testtttc.txt", "testc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntGreaterThan()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC > '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testtttc.txt", "testc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC IN('222', '223', '224', '225')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestc.txt", "restc.txt", "tesc.txt", "testtttc.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileIntNotIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:document where tas:IntPropertyC NOT IN('222', '223', '224')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testtttc.txt", "testc.txt", "testc1.txt", "testc2.txt", "testc3.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntEquality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF = '222'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntInequality()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF <> '223'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "testtttf", "testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntLessThan()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF < '223'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntLessThanOrEqual()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF <= '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "restf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntGreaterThanOrEqual()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF >= '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("tesf", "testtttf", "testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntGreaterThan()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF > '224'";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testtttf", "testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF IN('222', '223', '224', '225')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("guestf", "tesf", "restf", "testtttf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderIntNotIn()
|
||||
{
|
||||
String query = "SELECT * FROM tas:folder where tas:IntPropertyF NOT IN('222', '223', '224')";
|
||||
cmisApi.authenticateUser(testUser).withQuery(query).assertValues().isReturningValues("cmis:name",
|
||||
Set.of("testtttf", "testf", "testf1", "testf2", "testf3", "testf4"));
|
||||
}
|
||||
}
|
||||
|
||||
+92
-118
@@ -22,9 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -66,8 +64,8 @@ import org.testng.annotations.Test;
|
||||
* "gap": "+100DAY"
|
||||
* }
|
||||
* }
|
||||
* @author Michael Suzuki
|
||||
*
|
||||
* @author Michael Suzuki
|
||||
*/
|
||||
public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
{
|
||||
@@ -78,49 +76,37 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
waitForContentIndexing(file4.getContent(), true);
|
||||
}
|
||||
|
||||
/** Check the error messages mention the mandatory fields when they are omitted. */
|
||||
@Test
|
||||
@TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH }, executionType = ExecutionType.REGRESSION,
|
||||
description = "Check facet intervals mandatory fields")
|
||||
public void checkingFacetsMandatoryErrorMessages()
|
||||
{
|
||||
SearchRequest query = createQuery("cars");
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
ranges.add(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
|
||||
// Omit the field.
|
||||
query.setRanges(List.of(createRangesModel(null, "0", "400", "20")));
|
||||
query(query);
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError()
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "field"));
|
||||
ranges.clear();
|
||||
facetRangeModel.setField("content.size");
|
||||
ranges.add(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "field"));
|
||||
|
||||
// Omit the start.
|
||||
query.setRanges(List.of(createRangesModel("content.size", null, "400", "20")));
|
||||
query(query);
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError()
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "start"));
|
||||
facetRangeModel.setStart("0");
|
||||
ranges.clear();
|
||||
ranges.add(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "start"));
|
||||
|
||||
// Omit the end.
|
||||
query.setRanges(List.of(createRangesModel("content.size", "0", null, "20")));
|
||||
query(query);
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError()
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "end"));
|
||||
facetRangeModel.setEnd("400");
|
||||
query.setRanges(ranges);
|
||||
ranges.clear();
|
||||
ranges.add(facetRangeModel);
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "end"));
|
||||
|
||||
// Omit the gap.
|
||||
query.setRanges(List.of(createRangesModel("content.size", "0", "400", null)));
|
||||
query(query);
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError()
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "gap"));
|
||||
|
||||
facetRangeModel.setGap("100");
|
||||
.containsSummary(String.format(RestErrorModel.MANDATORY_PARAM, "gap"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,13 +117,8 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
{
|
||||
SearchRequest query = createQuery("* AND SITE:'" + testSite.getId() + "'");
|
||||
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField("content.size");
|
||||
facetRangeModel.setStart("0");
|
||||
facetRangeModel.setEnd("200");
|
||||
facetRangeModel.setGap("20");
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
ranges.add(facetRangeModel);
|
||||
RestRequestRangesModel facetRangeModel = createRangesModel("content.size", "0", "200", "20");
|
||||
List<RestRequestRangesModel> ranges = List.of(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
SearchResponse response = query(query);
|
||||
response.assertThat().entriesListIsNotEmpty();
|
||||
@@ -147,14 +128,14 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0);
|
||||
bucket.assertThat().field("label").is("[20 - 40)");
|
||||
bucket.assertThat().field("filterQuery").is("content.size:[\"20\" TO \"40\">");
|
||||
Map<String,String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
Map<String, String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket.");
|
||||
Map<String, String> info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"20");
|
||||
assertEquals(info.get("end"),"40");
|
||||
assertEquals(info.get("start"), "20");
|
||||
assertEquals(info.get("end"), "40");
|
||||
assertNull(info.get("count"));
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"false");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "false");
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(1);
|
||||
bucket.assertThat().field("label").is("[40 - 120)");
|
||||
@@ -162,20 +143,20 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket.");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"40");
|
||||
assertEquals(info.get("end"),"120");
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"false");
|
||||
assertEquals(info.get("start"), "40");
|
||||
assertEquals(info.get("end"), "120");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "false");
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(2);
|
||||
bucket.assertThat().field("label").is("[120 - 200]");
|
||||
bucket.assertThat().field("filterQuery").is("content.size:[\"120\" TO \"200\"]");
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket.");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"120");
|
||||
assertEquals(info.get("end"),"200");
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
assertEquals(info.get("start"), "120");
|
||||
assertEquals(info.get("end"), "200");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -186,14 +167,9 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
{
|
||||
SearchRequest query = createQuery("* AND SITE:'" + testSite.getId() + "'");
|
||||
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField("content.size");
|
||||
facetRangeModel.setStart("0");
|
||||
facetRangeModel.setEnd("200");
|
||||
facetRangeModel.setGap("20");
|
||||
RestRequestRangesModel facetRangeModel = createRangesModel("content.size", "0", "200", "20");
|
||||
facetRangeModel.setHardend(true);
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
ranges.add(facetRangeModel);
|
||||
List<RestRequestRangesModel> ranges = List.of(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
SearchResponse response = query(query);
|
||||
response.assertThat().entriesListIsNotEmpty();
|
||||
@@ -203,26 +179,26 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0);
|
||||
bucket.assertThat().field("label").is("[20 - 40)");
|
||||
bucket.assertThat().field("filterQuery").is("content.size:[\"20\" TO \"40\">");
|
||||
Map<String,String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
Map<String, String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket.");
|
||||
Map<String, String> info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"20");
|
||||
assertEquals(info.get("end"),"40");
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"false");
|
||||
assertEquals(info.get("start"), "20");
|
||||
assertEquals(info.get("end"), "40");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "false");
|
||||
assertNull(info.get("count"));
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(1);
|
||||
bucket.assertThat().field("label").is("[40 - 120)");
|
||||
bucket.assertThat().field("filterQuery").is("content.size:[\"40\" TO \"120\">");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"40");
|
||||
assertEquals(info.get("end"),"120");
|
||||
assertEquals(info.get("start"), "40");
|
||||
assertEquals(info.get("end"), "120");
|
||||
metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket.");
|
||||
assertNull(info.get("count"));
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"false");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "false");
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(2);
|
||||
bucket.assertThat().field("label").is("[120 - 200]");
|
||||
@@ -230,11 +206,11 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket.");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"120");
|
||||
assertEquals(info.get("end"),"200");
|
||||
assertEquals(info.get("start"), "120");
|
||||
assertEquals(info.get("end"), "200");
|
||||
assertNull(info.get("count"));
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
}
|
||||
|
||||
/** This test relies on a document created in 2015 existing, probably part of the sample site. */
|
||||
@@ -246,13 +222,8 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
{
|
||||
SearchRequest query = createQuery("name:A*");
|
||||
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField("created");
|
||||
facetRangeModel.setStart("2015-09-29T10:45:15.729Z");
|
||||
facetRangeModel.setEnd("2016-09-29T10:45:15.729Z");
|
||||
facetRangeModel.setGap("+280DAY");
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
ranges.add(facetRangeModel);
|
||||
RestRequestRangesModel facetRangeModel = createRangesModel("created", "2015-09-29T10:45:15.729Z", "2016-09-29T10:45:15.729Z", "+280DAY");
|
||||
List<RestRequestRangesModel> ranges = List.of(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
SearchResponse response = query(query);
|
||||
response.assertThat().entriesListIsNotEmpty();
|
||||
@@ -260,18 +231,18 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
RestGenericFacetResponseModel facetResponseModel = response.getContext().getFacets().get(0);
|
||||
|
||||
List<RestGenericBucketModel> buckets = facetResponseModel.getBuckets();
|
||||
assertThat(buckets.size(),is(1));
|
||||
assertThat(buckets.size(), is(1));
|
||||
|
||||
RestGenericBucketModel bucket = buckets.get(0);
|
||||
bucket.assertThat().field("label").is("[2015-09-29T10:45:15.729Z - 2017-04-11T10:45:15.729Z]");
|
||||
bucket.assertThat().field("filterQuery").is("created:[\"2015-09-29T10:45:15.729Z\" TO \"2017-04-11T10:45:15.729Z\"]");
|
||||
bucket.getMetrics().get(0).assertThat().field("value").is("{count=1}");
|
||||
Map<String, String> info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"2015-09-29T10:45:15.729Z");
|
||||
assertEquals(info.get("end"),"2017-04-11T10:45:15.729Z");
|
||||
assertNull(info.get("count"),"1");
|
||||
assertEquals(info.get("startInclusive"),"true");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
assertEquals(info.get("start"), "2015-09-29T10:45:15.729Z");
|
||||
assertEquals(info.get("end"), "2017-04-11T10:45:15.729Z");
|
||||
assertNull(info.get("count"), "1");
|
||||
assertEquals(info.get("startInclusive"), "true");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -280,19 +251,9 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
public void searchDateAndSizeRanges()
|
||||
{
|
||||
SearchRequest query = createQuery("* AND SITE:'" + testSite.getId() + "'");
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField("created");
|
||||
facetRangeModel.setStart("2015-09-29T10:45:15.729Z");
|
||||
facetRangeModel.setEnd("2016-09-29T10:45:15.729Z");
|
||||
facetRangeModel.setGap("+280DAY");
|
||||
ranges.add(facetRangeModel);
|
||||
RestRequestRangesModel facetCountRangeModel = new RestRequestRangesModel();
|
||||
facetCountRangeModel.setField("content.size");
|
||||
facetCountRangeModel.setStart("0");
|
||||
facetCountRangeModel.setEnd("500");
|
||||
facetCountRangeModel.setGap("200");
|
||||
ranges.add(facetCountRangeModel);
|
||||
RestRequestRangesModel facetRangeModel = createRangesModel("created", "2015-09-29T10:45:15.729Z", "2016-09-29T10:45:15.729Z", "+280DAY");
|
||||
RestRequestRangesModel facetCountRangeModel = createRangesModel("content.size", "0", "500", "200");
|
||||
List<RestRequestRangesModel> ranges = List.of(facetRangeModel, facetCountRangeModel);
|
||||
query.setRanges(ranges);
|
||||
}
|
||||
|
||||
@@ -304,16 +265,10 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
{
|
||||
SearchRequest query = createQuery("* AND SITE:'" + testSite.getId() + "'");
|
||||
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField("content.size");
|
||||
facetRangeModel.setStart("0");
|
||||
facetRangeModel.setEnd("200");
|
||||
facetRangeModel.setGap("20");
|
||||
List<String> include = new ArrayList<>();
|
||||
include.add("upper");
|
||||
RestRequestRangesModel facetRangeModel = createRangesModel("content.size", "0", "200", "20");
|
||||
List<String> include = List.of("upper");
|
||||
facetRangeModel.setInclude(include);
|
||||
List<RestRequestRangesModel> ranges = new ArrayList<>();
|
||||
ranges.add(facetRangeModel);
|
||||
List<RestRequestRangesModel> ranges = List.of(facetRangeModel);
|
||||
query.setRanges(ranges);
|
||||
SearchResponse response = query(query);
|
||||
response.assertThat().entriesListIsNotEmpty();
|
||||
@@ -323,14 +278,14 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
RestGenericBucketModel bucket = facetResponseModel.getBuckets().get(0);
|
||||
bucket.assertThat().field("label").is("(20 - 40]");
|
||||
bucket.assertThat().field("filterQuery").is("content.size:<\"20\" TO \"40\"]");
|
||||
Map<String,String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
Map<String, String> metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 2, "Unexpected count for first bucket.");
|
||||
Map<String, String> info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"20");
|
||||
assertEquals(info.get("end"),"40");
|
||||
assertEquals(info.get("start"), "20");
|
||||
assertEquals(info.get("end"), "40");
|
||||
assertNull(info.get("count"));
|
||||
assertEquals(info.get("startInclusive"),"false");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
assertEquals(info.get("startInclusive"), "false");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(1);
|
||||
bucket.assertThat().field("label").is("(40 - 120]");
|
||||
@@ -338,10 +293,10 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for second bucket.");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"40");
|
||||
assertEquals(info.get("end"),"120");
|
||||
assertEquals(info.get("startInclusive"),"false");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
assertEquals(info.get("start"), "40");
|
||||
assertEquals(info.get("end"), "120");
|
||||
assertEquals(info.get("startInclusive"), "false");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
|
||||
bucket = facetResponseModel.getBuckets().get(2);
|
||||
bucket.assertThat().field("label").is("(120 - 200]");
|
||||
@@ -349,9 +304,28 @@ public class FacetRangeSearchTest extends AbstractSearchServicesE2ETest
|
||||
metric = (Map<String, String>) bucket.getMetrics().get(0).getValue();
|
||||
assertEquals(Integer.valueOf(metric.get("count")).intValue(), 1, "Unexpected count for third bucket.");
|
||||
info = (Map<String, String>) bucket.getBucketInfo();
|
||||
assertEquals(info.get("start"),"120");
|
||||
assertEquals(info.get("end"),"200");
|
||||
assertEquals(info.get("startInclusive"),"false");
|
||||
assertEquals(info.get("endInclusive"),"true");
|
||||
}
|
||||
assertEquals(info.get("start"), "120");
|
||||
assertEquals(info.get("end"), "200");
|
||||
assertEquals(info.get("startInclusive"), "false");
|
||||
assertEquals(info.get("endInclusive"), "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ranges model with the values given.
|
||||
*
|
||||
* @param field The field to facet on.
|
||||
* @param start The lowest facet value.
|
||||
* @param end The highest facet value.
|
||||
* @param gap The size of the buckets.
|
||||
* @return The facet ranges model.
|
||||
*/
|
||||
private RestRequestRangesModel createRangesModel(String field, String start, String end, String gap)
|
||||
{
|
||||
RestRequestRangesModel facetRangeModel = new RestRequestRangesModel();
|
||||
facetRangeModel.setField(field);
|
||||
facetRangeModel.setStart(start);
|
||||
facetRangeModel.setEnd(end);
|
||||
facetRangeModel.setGap(gap);
|
||||
return facetRangeModel;
|
||||
}
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2019 Alfresco Software, Ltd. All rights reserved.
|
||||
* License rights for this program may be obtained from Alfresco Software, Ltd.
|
||||
* pursuant to a written agreement and any use of this program without such an
|
||||
* agreement is prohibited.
|
||||
*/
|
||||
|
||||
package org.alfresco.test.search.functional.searchServices.search;
|
||||
|
||||
import static java.util.List.of;
|
||||
|
||||
import static jersey.repackaged.com.google.common.collect.Sets.newHashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.alfresco.rest.model.RestNodeAssociationModelCollection;
|
||||
import org.alfresco.rest.model.RestNodeChildAssociationModel;
|
||||
import org.alfresco.rest.search.SearchResponse;
|
||||
import org.alfresco.search.TestGroup;
|
||||
import org.alfresco.test.search.functional.AbstractE2EFunctionalTest;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* Test class tests AFTS Search In Field works
|
||||
* Created for Search-840
|
||||
*
|
||||
* @author Meenal Bhave
|
||||
*/
|
||||
public class SearchAFTSInFieldTest extends AbstractE2EFunctionalTest
|
||||
{
|
||||
private FolderModel folder1, folder2;
|
||||
private FileModel file1, file2, file3, file4, file5;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
// Create Folders:
|
||||
// Folder1: Expected to be found with file.txt
|
||||
folder1 = new FolderModel("file txt folder");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createFolder(folder1);
|
||||
|
||||
// Folder2: Not expected to be found with file.txt
|
||||
folder2 = new FolderModel("txt files folder");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createFolder(folder2);
|
||||
|
||||
// Create File(s): Expected to be found with file.txt
|
||||
file1 = new FileModel("file.txt", "file.txt", "", FileType.TEXT_PLAIN, "file.txt");
|
||||
|
||||
file2 = new FileModel("1-file.txt", "1-file.txt", "", FileType.TEXT_PLAIN, "1-file.txt");
|
||||
|
||||
file3 = new FileModel("file1.txt", "file1.txt", "", FileType.TEXT_PLAIN, "file1.txt");
|
||||
|
||||
file4 = new FileModel("txt file", "txt file", "", FileType.TEXT_PLAIN, "txt file");
|
||||
|
||||
// Not Expected to be found with file.txt
|
||||
file5 = new FileModel("txt files", "txt files", "", FileType.TEXT_PLAIN, "txt files");
|
||||
|
||||
of(file1, file2, file3, file4, file5).forEach(
|
||||
f -> dataContent.usingUser(testUser).usingSite(testSite).usingResource(folder1).createContent(f));
|
||||
|
||||
waitForContentIndexing(file5.getContent(), true);
|
||||
}
|
||||
|
||||
@Test(priority = 1, groups = { TestGroup.ACS_63n })
|
||||
public void testSearchInFieldName()
|
||||
{
|
||||
// Field names in various formats
|
||||
Stream<String> fieldNames = Stream.of("{http://www.alfresco.org/model/content/1.0}name",
|
||||
"@{http://www.alfresco.org/model/content/1.0}name",
|
||||
"cm_name",
|
||||
"cm:name",
|
||||
"@cm:name",
|
||||
"name");
|
||||
|
||||
// For each field name, check that queries return consistent results with / out ''
|
||||
fieldNames.forEach(fieldName ->
|
||||
{
|
||||
// Query string without quotes
|
||||
String query = fieldName + ":file.txt";
|
||||
|
||||
Set<String> expectedNames = newHashSet();
|
||||
expectedNames.add("file.txt"); // file1
|
||||
expectedNames.add("1-file.txt"); // file2
|
||||
expectedNames.add("file1.txt"); // file3
|
||||
expectedNames.add("txt file"); // file4
|
||||
expectedNames.add("file txt folder"); // folder1
|
||||
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
|
||||
// Query string in single quotes
|
||||
query = fieldName + ":'file.txt'";
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test(priority = 2, groups = { TestGroup.ACS_63n })
|
||||
public void testSearchInFieldTitle()
|
||||
{
|
||||
// Field names in various formats
|
||||
Stream<String> fieldNames = Stream.of("{http://www.alfresco.org/model/content/1.0}title",
|
||||
"@{http://www.alfresco.org/model/content/1.0}title",
|
||||
"cm_title",
|
||||
"cm:title",
|
||||
"@cm:title");
|
||||
|
||||
// For each field name, check that queries return consistent results with / out ''
|
||||
fieldNames.forEach(fieldName ->
|
||||
{
|
||||
String query = fieldName + ":" + file2.getName();
|
||||
boolean fileFound = isContentInSearchResults(query, file2.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, 1, SearchLanguage.AFTS);
|
||||
|
||||
query = fieldName + ":'" + file2.getName() + "\'";
|
||||
fileFound = isContentInSearchResults(query, file2.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, 1, SearchLanguage.AFTS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test(priority = 3, groups = { TestGroup.ACS_63n })
|
||||
public void testSearchInFieldContent()
|
||||
{
|
||||
// Field names in various formats
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
fieldNames.add("TEXT");
|
||||
fieldNames.add("{http://www.alfresco.org/model/dictionary/1.0}content");
|
||||
fieldNames.add("cm:content");
|
||||
fieldNames.add("d:content");
|
||||
|
||||
// For each field name, check that queries return consistent results with / out ''
|
||||
fieldNames.forEach(fieldName ->
|
||||
{
|
||||
String query = fieldName + ":" + file3.getContent();
|
||||
boolean fileFound = isContentInSearchResults(query, file3.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
Integer resultCount1 = testSearchQuery(query, null, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
|
||||
query = fieldName + ":'" + file3.getContent() + "\'";
|
||||
fileFound = isContentInSearchResults(query, file3.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, resultCount1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
});
|
||||
}
|
||||
|
||||
@Test(priority = 4)
|
||||
public void testSearchInFieldSITE()
|
||||
{
|
||||
String query = "SITE:" + testSite.getId();
|
||||
boolean fileFound = isContentInSearchResults(query, folder1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Site Not found for query: " + query);
|
||||
|
||||
Integer resultCount1 = testSearchQuery(query, 8, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
|
||||
query = "SITE:'" + testSite.getId() + "\'";
|
||||
fileFound = isContentInSearchResults(query, folder1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Site Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, resultCount1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
}
|
||||
|
||||
@Test(priority = 5)
|
||||
public void testSearchInFieldTYPE()
|
||||
{
|
||||
// Field names in various formats
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
fieldNames.add("TYPE");
|
||||
fieldNames.add("EXACTTYPE");
|
||||
|
||||
// For each field name, check that queries return consistent results with / out ''
|
||||
fieldNames.forEach(fieldName -> {
|
||||
|
||||
String query = fieldName + ":cm\\:content" + " and =cm:name:" + file1.getName();
|
||||
boolean fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Content Not found for query: " + query);
|
||||
|
||||
Integer resultCount1 = testSearchQuery(query, 1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
|
||||
query = fieldName + ":'cm:content'" + " and =cm:name:" + file1.getName();
|
||||
fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Content Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, resultCount1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
});
|
||||
}
|
||||
|
||||
@Test(priority = 6)
|
||||
public void testSearchInFieldID() throws Exception
|
||||
{
|
||||
String query = "ID:'workspace://SpacesStore/" + file1.getNodeRefWithoutVersion() + "'";
|
||||
boolean fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Content Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, 1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
}
|
||||
|
||||
@Test(priority = 7)
|
||||
public void testSearchInFieldPARENT()
|
||||
{
|
||||
String query = "PARENT:" + folder1.getNodeRefWithoutVersion();
|
||||
|
||||
Set<String> expectedNames = newHashSet();
|
||||
expectedNames.add(file1.getName());
|
||||
expectedNames.add(file2.getName());
|
||||
expectedNames.add(file3.getName());
|
||||
expectedNames.add(file4.getName());
|
||||
expectedNames.add(file5.getName());
|
||||
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
|
||||
query = "PARENT:'" + folder1.getNodeRefWithoutVersion() + "\'";
|
||||
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
}
|
||||
|
||||
@Test(priority = 8)
|
||||
public void testSearchInFieldPRIMARYPARENT() throws Exception
|
||||
{
|
||||
// Create Secondary association in testFolder2
|
||||
RestNodeChildAssociationModel childAssoc1 = new RestNodeChildAssociationModel(file1.getNodeRefWithoutVersion(), "cm:contains");
|
||||
String secondaryChildrenBody = "[" + childAssoc1.toJson() + "]";
|
||||
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingResource(folder2).createSecondaryChildren(secondaryChildrenBody);
|
||||
RestNodeAssociationModelCollection secondaryChildren = restClient.authenticateUser(testUser).withCoreAPI().usingResource(folder2).getSecondaryChildren();
|
||||
secondaryChildren.getEntryByIndex(0).assertThat().field("id").is(file1.getNodeRefWithoutVersion());
|
||||
|
||||
String query = "PRIMARYPARENT:'workspace://SpacesStore/" + folder1.getNodeRef() + "'";
|
||||
|
||||
Set<String> expectedNames = newHashSet();
|
||||
expectedNames.add(file1.getName());
|
||||
expectedNames.add(file2.getName());
|
||||
expectedNames.add(file3.getName());
|
||||
expectedNames.add(file4.getName());
|
||||
expectedNames.add(file5.getName());
|
||||
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
|
||||
query = "PARENT:'workspace://SpacesStore/" + folder2.getNodeRef() + "'";
|
||||
boolean fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "Expected Content Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, 1, SearchLanguage.AFTS);
|
||||
}
|
||||
|
||||
@Test(priority = 9, groups = { TestGroup.ACS_63n })
|
||||
public void testSearchInFieldNameExactMatch()
|
||||
{
|
||||
// Check that queries return consistent results with / out ''
|
||||
String query = "=name:" + file1.getName();
|
||||
boolean fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
Integer resultCount1 = testSearchQuery(query, 1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
Assert.assertSame(resultCount1, 1, "File count does not match for query: " + query);
|
||||
|
||||
query = "=name:'" + file1.getName() + "\'";
|
||||
fileFound = isContentInSearchResults(query, file1.getName(), true);
|
||||
Assert.assertTrue(fileFound, "File Not found for query: " + query);
|
||||
|
||||
testSearchQuery(query, resultCount1, SearchLanguage.AFTS).getPagination().getTotalItems();
|
||||
}
|
||||
|
||||
@Test(priority = 10, groups = { TestGroup.ACS_63n })
|
||||
public void testSearchInFieldNameQueryExpansion()
|
||||
{
|
||||
// Check that queries return consistent results with / out ''
|
||||
String query = "~name:" + file1.getName();
|
||||
|
||||
Set<String> expectedNames = newHashSet();
|
||||
expectedNames.add(file1.getName());
|
||||
expectedNames.add(file2.getName());
|
||||
expectedNames.add(file3.getName());
|
||||
expectedNames.add(file4.getName());
|
||||
expectedNames.add(folder1.getName());
|
||||
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
|
||||
query = "~name:'" + file1.getName() + "\'";
|
||||
testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
}
|
||||
|
||||
@Test(priority = 11, groups = { TestGroup.ACS_63n })
|
||||
public void testWithConjunctionDisjunctionAndNegation()
|
||||
{
|
||||
// Query string to include Conjunction, Disjunction and Negation
|
||||
|
||||
String query1 = "~name:" + file1.getName(); // Query expected to return 5 results
|
||||
String query2 = "=name:" + file2.getName(); // Query expected to return 1 result
|
||||
String query3 = "=name:" + file3.getName(); // Query expected to return 1 result
|
||||
|
||||
// Check Query with Conjunction, Negation, Disjunction: returns right results
|
||||
// "~name:file.txt and ! (=name:1-file.txt or =name:file1.txt)"
|
||||
String query = query1 + " and ! (" + query2 + " or " + query3 + ")";
|
||||
|
||||
// Check that expected files are included in the results
|
||||
Set<String> expectedNames = newHashSet();
|
||||
expectedNames.add("file.txt"); // file1
|
||||
expectedNames.add("txt file"); // file4
|
||||
expectedNames.add("file txt folder"); // folder1
|
||||
|
||||
SearchResponse response = testSearchQueryUnordered(query, expectedNames, SearchLanguage.AFTS);
|
||||
|
||||
// Check result count is 5-(1+1)=3
|
||||
int resultCount = response.getPagination().getTotalItems();
|
||||
Assert.assertEquals(resultCount, 3, "File count does not match for query: " + query);
|
||||
|
||||
// Check that file2 and file3 are excluded from the results
|
||||
boolean fileFound = isContentInSearchResponse(response, file2.getName());
|
||||
Assert.assertFalse(fileFound, "File2 found for query: " + query);
|
||||
|
||||
fileFound = isContentInSearchResponse(response, file3.getName());
|
||||
Assert.assertFalse(fileFound, "File3 found for query: " + query);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2019 Alfresco Software, Ltd. All rights reserved.
|
||||
* License rights for this program may be obtained from Alfresco Software, Ltd.
|
||||
* pursuant to a written agreement and any use of this program without such an
|
||||
* agreement is prohibited.
|
||||
*/
|
||||
|
||||
package org.alfresco.test.search.functional.searchServices.search;
|
||||
|
||||
import org.alfresco.rest.model.RestNodeAssociationModelCollection;
|
||||
import org.alfresco.rest.model.RestNodeChildAssociationModel;
|
||||
import org.alfresco.test.search.functional.AbstractE2EFunctionalTest;
|
||||
import org.alfresco.utility.data.CustomObjectTypeProperties;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* Test class tests content in the secondary parent is found too
|
||||
* Created for Search-1313
|
||||
*
|
||||
* @author Meenal Bhave
|
||||
*/
|
||||
public class SearchSecondaryAssociationTest extends AbstractE2EFunctionalTest
|
||||
{
|
||||
private FolderModel testFolder1, testFolder2;
|
||||
private FileModel file1;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
// Folders
|
||||
testFolder1 = new FolderModel("folder1");
|
||||
testFolder2 = new FolderModel("folder2");
|
||||
|
||||
// File(s)
|
||||
file1 = new FileModel("file1.txt");
|
||||
file1.setContent("content file 1");
|
||||
|
||||
// Create folder1
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createCustomContent(testFolder1, "cmis:folder", new CustomObjectTypeProperties());
|
||||
|
||||
// Create file1
|
||||
dataContent.usingUser(testUser).usingResource(testFolder1).createCustomContent(file1, "cmis:document", new CustomObjectTypeProperties());
|
||||
|
||||
// Create folder2
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createCustomContent(testFolder2, "cmis:folder", new CustomObjectTypeProperties());
|
||||
|
||||
// wait for solr index
|
||||
waitForMetadataIndexing(file1.getName(), true);
|
||||
}
|
||||
|
||||
@Test(priority = 1)
|
||||
public void testSearchPathForSecondaryAssociation() throws Exception
|
||||
{
|
||||
String queryPathFolder1 = "PATH:\"/app:company_home/st:sites/cm:" + testSite.getTitle() +
|
||||
"/cm:documentLibrary/cm:" + testFolder1.getName() + "/cm:" + file1.getName() + "\"";
|
||||
|
||||
// Test if file can be found in folder1: Primary Parent
|
||||
boolean found = isContentInSearchResults(queryPathFolder1, file1.getName(), true);
|
||||
Assert.assertTrue(found, "File Not found using Primary Parent Path");
|
||||
|
||||
String queryPathFolder2 = "PATH:\"/app:company_home/st:sites/cm:" + testSite.getTitle() +
|
||||
"/cm:documentLibrary/cm:" + testFolder2.getName() + "/cm:" + file1.getName() + "\"";
|
||||
|
||||
// Test if file can not be found in folder2
|
||||
found = isContentInSearchResults(queryPathFolder2, file1.getName(), false);
|
||||
Assert.assertTrue(found, "File found using Secondary Parent Path");
|
||||
|
||||
// Create Secondary association in folder2
|
||||
RestNodeChildAssociationModel childAssoc1 = new RestNodeChildAssociationModel(file1.getNodeRefWithoutVersion(), "cm:contains");
|
||||
String secondaryChildrenBody = "[" + childAssoc1.toJson() + "]";
|
||||
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingResource(testFolder2).createSecondaryChildren(secondaryChildrenBody);
|
||||
RestNodeAssociationModelCollection secondaryChildren = restClient.authenticateUser(testUser).withCoreAPI().usingResource(testFolder2).getSecondaryChildren();
|
||||
secondaryChildren.getEntryByIndex(0).assertThat().field("id").is(file1.getNodeRefWithoutVersion());
|
||||
|
||||
// Test if file can be found in folder2: Secondary Parent
|
||||
found = isContentInSearchResults(queryPathFolder2, file1.getName(), true);
|
||||
Assert.assertTrue(found, "File Not found using Secondary Parent Path");
|
||||
|
||||
// Remove Secondary association
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingResource(testFolder2).deleteSecondaryChild(secondaryChildren.getEntryByIndex(0));
|
||||
|
||||
// Test if file can not be found in folder2
|
||||
found = isContentInSearchResults(queryPathFolder2, file1.getName(), false);
|
||||
Assert.assertTrue(found, "File found using Secondary Parent Path");
|
||||
}
|
||||
}
|
||||
+336
-19
@@ -18,12 +18,16 @@
|
||||
*/
|
||||
package org.alfresco.test.search.functional.searchServices.search;
|
||||
|
||||
import org.alfresco.dataprep.SiteService.Visibility;
|
||||
import org.alfresco.rest.model.RestRequestSpellcheckModel;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.rest.search.SearchResponse;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.search.TestGroup;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
@@ -141,7 +145,7 @@ public class SearchSpellCheckTest extends AbstractSearchServicesE2ETest
|
||||
assertResponse(query(searchReq));
|
||||
}
|
||||
|
||||
@Test(groups={ TestGroup.ACS_60n}, priority=3)
|
||||
@Test(groups = { TestGroup.ACS_60n }, priority = 3)
|
||||
public void testSearchWithSpellcheckerAndCorrectSpelling()
|
||||
{
|
||||
SearchRequest searchReq = new SearchRequest();
|
||||
@@ -154,28 +158,341 @@ public class SearchSpellCheckTest extends AbstractSearchServicesE2ETest
|
||||
Assert.assertNull(res.getContext().getSpellCheck());
|
||||
res.assertThat().entriesListIsNotEmpty();
|
||||
}
|
||||
|
||||
@Test(groups={TestGroup.ACS_60n}, priority=4)
|
||||
|
||||
/**
|
||||
* Test to check the different spellcheck types searchInsteadFor and didYouMean
|
||||
* Search suggestion is based on maxEdits, count of entries, Alphabetical
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(groups = { TestGroup.ACS_60n }, priority = 4)
|
||||
public void testSpellCheckType() throws Exception
|
||||
{
|
||||
// Create a file with mis-spelt name, expect spellcheck type = didYouMean
|
||||
FileModel file = new FileModel(unique_searchString + "-1.txt", "uniquee" + "uniquee", "uniquee", FileType.TEXT_PLAIN, "Unique text file for search ");
|
||||
// Create a file with word in cm:name only
|
||||
FileModel file = new FileModel("learning", "", "", FileType.TEXT_PLAIN, "");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file);
|
||||
|
||||
waitForIndexing(file.getName(), true);
|
||||
// Wait for the file to be indexed
|
||||
Assert.assertTrue(waitForMetadataIndexing(file.getName(), true));
|
||||
|
||||
// Correct spelling with cm:name field
|
||||
SearchResponse response = SearchSpellcheckQuery(testUser, "cm:name:learning", "learning");
|
||||
|
||||
response.assertThat().entriesListIsNotEmpty();
|
||||
response.getContext().assertThat().field("spellCheck").isNull();
|
||||
|
||||
// Matching Result, No Spellcheck object returned
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Correct spelling with no specific field
|
||||
response = SearchSpellcheckQuery(testUser, "learning", "learning");
|
||||
|
||||
// Matching Result, No Spellcheck object returned
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Correct spelling with a different field. Used cm:content field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:content:learning", "learning");
|
||||
|
||||
// 0 Results, No Spellcheck object returned
|
||||
response.assertThat().entriesListIsEmpty();
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Incorrect spelling with cm:name field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:name:lerning", "lerning");
|
||||
|
||||
// 1 Match with right spelling and Spellcheck = SearchInsteadFor: lerning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
// TODO: Investigate: Share shows searchInsteadFor = lerning, API shows learning
|
||||
// testSearchSpellcheckResponse(response, "searchInsteadFor", "lerning");
|
||||
|
||||
// Incorrect spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "lerning", "lerning");
|
||||
|
||||
// 1 Match with right spelling and Spellcheck = SearchInsteadFor: lerning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
// TODO: Investigate: Share shows searchInsteadFor = lerning, API shows learning
|
||||
// testSearchSpellcheckResponse(response, "searchInsteadFor", "lerning");
|
||||
|
||||
// Incorrect spelling with a different field. Used cm:content field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:content:lerning", "lerning");
|
||||
|
||||
// 0 Results, No Spellcheck object returned
|
||||
response.assertThat().entriesListIsEmpty();
|
||||
response.getContext().assertThat().field("spellCheck").isNull();
|
||||
|
||||
// Create a file with word with 1 max Edit in cm:name only
|
||||
FileModel file2 = new FileModel("leaning", "", "", FileType.TEXT_PLAIN, "");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file2);
|
||||
|
||||
// Create a file with word in cm:name and cm:content only
|
||||
FileModel file3 = new FileModel("leaning 2", "", "", FileType.TEXT_PLAIN, "leaning 2");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file3);
|
||||
|
||||
Assert.assertTrue(waitForContentIndexing(file3.getContent(), true));
|
||||
|
||||
// Incorrect spelling with cm:name field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:name:lerning", "lerning");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file3.getName()), "Expected file not returned in the search results: " + file3.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "leaning");
|
||||
|
||||
// Incorrect spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "lerning", "lerning");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file3.getName()), "Expected file not returned in the search results: " + file3.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "leaning");
|
||||
|
||||
// Incorrect spelling with cm:content field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:content:lerning", "lerning");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file3.getName()), "Expected file not returned in the search results: " + file3.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "leaning");
|
||||
|
||||
// Correct spelling with cm:name field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:name:learning", "learning");
|
||||
|
||||
// Matching Result, Spellcheck = didYouMean: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "didYouMean", "leaning");
|
||||
|
||||
// Correct spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "learning", "learning");
|
||||
|
||||
// Matching Result, Spellcheck = didYouMean: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "didYouMean", "leaning");
|
||||
|
||||
// Correct spelling with cm:content field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:content:learning", "learning");
|
||||
|
||||
// Matching Result, Spellcheck = didYouMean: leaning
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file3.getName()), "Expected file not returned in the search results: " + file3.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "leaning");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a test for the spellcheck parameters minEdit and maxPrefix
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(groups = { TestGroup.ACS_60n }, priority = 5)
|
||||
public void testSpellCheckParameters() throws Exception
|
||||
{
|
||||
// Create a file with word in cm:name and cm:content
|
||||
FileModel file = new FileModel("eklipse", "", "", FileType.TEXT_PLAIN, "eklipse");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file);
|
||||
|
||||
// Create a file with word in cm:name, cm:title and cm:content
|
||||
FileModel file2 = new FileModel("eklipses", "eklipses", "", FileType.TEXT_PLAIN, "eklipses");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file2);
|
||||
|
||||
Assert.assertTrue(waitForContentIndexing(file2.getName(), true));
|
||||
|
||||
// Search with field not filed in either files
|
||||
SearchResponse response = SearchSpellcheckQuery(testUser, "cm:description:'eclipse'", "eclipse");
|
||||
|
||||
// 0 Results, Spellcheck not returned
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Incorrect spelling with the field on a file as well
|
||||
response = SearchSpellcheckQuery(testUser, "cm:name:'eclipse'", "eclipse");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: eklipse
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "eklipse");
|
||||
|
||||
// Incorrect spelling with no field for file1
|
||||
response = SearchSpellcheckQuery(testUser, "eclipse", "eclipse");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: eklipse
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "eklipse");
|
||||
|
||||
// Incorrect spelling with no field for file2
|
||||
response = SearchSpellcheckQuery(testUser, "eclipses", "eclipses");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: eklipses
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "eklipses");
|
||||
|
||||
// Search for the field only filed on file2 and not file1
|
||||
response = SearchSpellcheckQuery(testUser, "cm:title:'eclipses'", "eclipses");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: eklipses
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "eklipses");
|
||||
|
||||
// Query using 3 edits (more than spellcheck works for [maxEdits<=2])
|
||||
response = SearchSpellcheckQuery(testUser, "elapssed", "elapssed");
|
||||
|
||||
// 0 Results, No Spellcheck object returned
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Query with edit on first letter (does not work with spellcheck [minPrefix=1])
|
||||
response = SearchSpellcheckQuery(testUser, "iklipse ", "iklipse ");
|
||||
|
||||
// 0 Results, No Spellcheck object returned
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a test to check the fields defined for spellcheck in
|
||||
* shared.properties work cm:name, cm:title, cm:description and cm:content are
|
||||
* defined in shared.properties
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(groups = { TestGroup.ACS_60n }, priority = 6)
|
||||
public void testSpellCheckFields() throws Exception
|
||||
{
|
||||
// Create a file with same word in all fields
|
||||
FileModel file = new FileModel("book", "book", "book", FileType.TEXT_PLAIN, "book");
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file);
|
||||
|
||||
Assert.assertTrue(waitForContentIndexing(file.getContent(), true));
|
||||
|
||||
// Incorrect spelling with no field
|
||||
SearchResponse response = SearchSpellcheckQuery(testUser, "bo0k", "bo0k");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: book
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "book");
|
||||
|
||||
// Incorrect spelling with the cm:name field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:name:'bo0k'", "bo0k");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: book
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "book");
|
||||
|
||||
// Incorrect spelling with the cm:title field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:title:'bo0k'", "bo0k");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: book
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "book");
|
||||
|
||||
// Incorrect spelling with the cm:description field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:description:'bo0k'", "bo0k");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: book
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "book");
|
||||
|
||||
// Incorrect spelling with the cm:description field
|
||||
response = SearchSpellcheckQuery(testUser, "cm:content:'bo0k'", "bo0k");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: book
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file.getName()), "Expected file not returned in the search results: " + file.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "book");
|
||||
|
||||
// Incorrect spelling with the cm:author field (not suggestable field for Spellcheck
|
||||
response = SearchSpellcheckQuery(testUser, "cm:author:'bo0k'", "bo0k");
|
||||
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a test to check the ACL tracker works with spellcheck enabled
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(groups = { TestGroup.ACS_60n }, priority = 7)
|
||||
public void testSpellCheckACL() throws Exception
|
||||
{
|
||||
// Create User 2
|
||||
testUser2 = dataUser.createRandomTestUser("User2");
|
||||
|
||||
// Create Private Site 2
|
||||
testSite2 = new SiteModel(RandomData.getRandomName("Site2"));
|
||||
testSite2.setVisibility(Visibility.PRIVATE);
|
||||
|
||||
testSite2 = dataSite.usingUser(testUser).createSite(testSite2);
|
||||
|
||||
// Make User 2 Site Collaborator
|
||||
getDataUser().addUserToSite(testUser2, testSite2, UserRole.SiteCollaborator);
|
||||
|
||||
// Add file <spacebar> to testSite
|
||||
FileModel file1 = new FileModel("spacebar", "", "", FileType.TEXT_PLAIN, "spacebar");
|
||||
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file1);
|
||||
|
||||
// Add file <spacecar> to testSite, testSite2
|
||||
FileModel file2 = new FileModel("spacecar", "", "", FileType.TEXT_PLAIN, "spacecar");
|
||||
|
||||
dataContent.usingUser(testUser).usingSite(testSite).createContent(file2);
|
||||
dataContent.usingUser(testUser).usingSite(testSite2).createContent(file2);
|
||||
|
||||
Assert.assertTrue(waitForContentIndexing(file2.getContent(), true));
|
||||
|
||||
// Checks for User 2
|
||||
// Incorrect spelling with no field
|
||||
SearchResponse response = SearchSpellcheckQuery(testUser, "spaceber", "spaceber");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacebar: alphabetical
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "spacebar");
|
||||
|
||||
// Search
|
||||
SearchRequest searchReq = new SearchRequest();
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery("cm:title:uniquee");
|
||||
queryReq.setUserQuery("uniquee");
|
||||
searchReq.setQuery(queryReq);
|
||||
searchReq.setSpellcheck(new RestRequestSpellcheckModel());
|
||||
SearchResponse nodes = query(searchReq);
|
||||
|
||||
nodes.assertThat().entriesListIsNotEmpty();
|
||||
nodes.getContext().assertThat().field("spellCheck").isNotEmpty();
|
||||
nodes.getContext().getSpellCheck().assertThat().field("suggestions").contains("unique");
|
||||
nodes.getContext().getSpellCheck().assertThat().field("type").is("didYouMean");
|
||||
// Correct spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "spacebar", "spacebar");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacebar
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file1.getName());
|
||||
testSearchSpellcheckResponse(response, "didYouMean", "spacecar");
|
||||
|
||||
// Incorrect spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "spacebra", "spacebra");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacebar
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file1.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "spacebar");
|
||||
|
||||
// Correct spelling with no field
|
||||
response = SearchSpellcheckQuery(testUser, "spacecar", "spacecar");
|
||||
|
||||
// Matching Result, Spellcheck Not returned
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
|
||||
// Checks for User 2
|
||||
// Incorrect spelling for files created no field
|
||||
response = SearchSpellcheckQuery(testUser2, "spaceber", "spaceber");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacecar
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "spacecar");
|
||||
|
||||
// correct spelling no field
|
||||
response = SearchSpellcheckQuery(testUser2, "spacebar", "spacebar");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacecar
|
||||
Assert.assertFalse(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file1.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "spacecar");
|
||||
|
||||
// Incorrect spelling no field
|
||||
response = SearchSpellcheckQuery(testUser2, "spacecra", "spacecra");
|
||||
|
||||
// Matching Result, Spellcheck = searchInsteadFor: spacebar
|
||||
Assert.assertFalse(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file1.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, "searchInsteadFor", "spacecar");
|
||||
|
||||
// Correct spelling no field
|
||||
response = SearchSpellcheckQuery(testUser2, "spacecar", "spacecar");
|
||||
|
||||
// Matching Result, Spellcheck not returned
|
||||
Assert.assertFalse(isContentInSearchResponse(response, file1.getName()), "Expected file not returned in the search results: " + file1.getName());
|
||||
Assert.assertTrue(isContentInSearchResponse(response, file2.getName()), "Expected file not returned in the search results: " + file2.getName());
|
||||
testSearchSpellcheckResponse(response, null, null);
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xsi:noNamespaceSchemaLocation="https://gitlab.alfresco.com/tas/tas-namespaces/raw/master/input-test-data.xsd">
|
||||
|
||||
<queries>
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name = 'testc.txt'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name <> 'testc.txt'"
|
||||
expectedResults="7" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name < 'testc.txt'"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name <= 'testc.txt'"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name > 'testc.txt'"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name >= 'testc.txt'"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name IN('testc.txt', 'guestc.txt', 'restc.txt')"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name NOT IN('testc.txt', 'gustc.txt', 'restc.txt')"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE '%testc%'"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE 'testc.txt'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE 't%tc.txt'"
|
||||
expectedResults="2" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE 't__tc.txt'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name = 'testf'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name <> 'testf'"
|
||||
expectedResults="8" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name < 'testf'"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name <= 'testf'"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name > 'testf'"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name >= 'testf'"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name IN('testf', 'guestf', 'restf')"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name NOT IN('testf', 'gustf', 'restf')"
|
||||
expectedResults="7" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE '%testf%'"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE 'testf'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE 't%tf'"
|
||||
expectedResults="2" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE 't__tf'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name ASC"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name DESC"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where cmis:name LIKE '%testc%' ORDER BY cmis:name DESC, cmis:creationDate ASC"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name ASC"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name DESC"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where cmis:name LIKE '%testf%' ORDER BY cmis:name DESC, cmis:creationDate ASC"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC = 'restc text'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC <> 'testc1 text'"
|
||||
expectedResults="7" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC IN('restc text', 'testc2 text', 'testc3 text')"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC NOT IN('restc text', 'testc2 text', 'testc3 text')"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC LIKE '%restc%'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC >= 'testc'"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:TextPropertyC LIKE 't__tc text'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF = 'restf text'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF <> 'testf1 text'"
|
||||
expectedResults="8" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF IN('restf text', 'testf2 text', 'testf3 text')"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF NOT IN('restf text', 'testf2 text', 'testf3 text')"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF LIKE '%restf%'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF >= 'testf text'"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:TextPropertyF LIKE 't__tf text'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC = '222'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC <> '223'"
|
||||
expectedResults="7" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC < '223'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC <= '224'"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC >= '224'"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC > '224'"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC IN('222', '223', '224', '225')"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:document where tas:IntPropertyC NOT IN('222', '223', '224')"
|
||||
expectedResults="5" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF = '222'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF <> '223'"
|
||||
expectedResults="8" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF < '223'"
|
||||
expectedResults="1" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF <= '224'"
|
||||
expectedResults="3" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF >= '224'"
|
||||
expectedResults="7" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF > '224'"
|
||||
expectedResults="6" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF IN('222', '223', '224', '225')"
|
||||
expectedResults="4" />
|
||||
<query
|
||||
value="SELECT * FROM tas:folder where tas:IntPropertyF NOT IN('222', '223', '224')"
|
||||
expectedResults="6" />
|
||||
</queries>
|
||||
</testData>
|
||||
@@ -29,7 +29,7 @@
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<solr.base.version>6.6.5</solr.base.version>
|
||||
<solr.version>${solr.base.version}-patched.1</solr.version>
|
||||
<solr.version>${solr.base.version}-patched.2</solr.version>
|
||||
<!-- The location to download the solr zip file from. -->
|
||||
<!-- <solr.zip>https://archive.apache.org/dist/lucene/solr/${solr.version}/solr-${solr.version}.zip</solr.zip> -->
|
||||
<!-- Solr startup scripts do not work with any Java version higher than 9 so the scripts have been patched -->
|
||||
|
||||
@@ -338,6 +338,22 @@ This Docker Image is available at Alfresco Docker Hub:
|
||||
To use the public image instead of the local one (`searchservices:develop`) just use `alfresco/alfresco-search-services:1.3.x.x` labels.
|
||||
|
||||
|
||||
## Docker Master-Slave setup
|
||||
### Enable Search Slave Replica config
|
||||
|
||||
To enable slave node specify environment value `REPLICATION_TYPE=slave`, by default Master config is enabled and slave is disabled.
|
||||
|
||||
During deployment time whenever Search Services or Insight Engine image starts, it will execute the script [search_config_setup.sh](/packaging/src/docker) which will configure the slave config setup based on the value specified in the script.
|
||||
|
||||
To run the docker image:
|
||||
|
||||
```bash
|
||||
$ docker run -p 8984:8983 -e REPLICATION_TYPE=slave -e ALFRESCO_SECURE_COMMS=none -e SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive searchservices:develop
|
||||
```
|
||||
Solr-slave End point: [http://localhost:8984/solr](http://localhost:8984/solr)
|
||||
|
||||
To generate your own Docker-compose file please follow [generator-alfresco-docker-compose](../e2e-test/generator-alfresco-docker-compose/README.md)
|
||||
|
||||
### Use Alfresco Search Services Docker Image with Docker Compose
|
||||
|
||||
Sample configuration in a Docker Compose file using **Plain HTTP** protocol to communicate with Alfresco Repository.
|
||||
|
||||
@@ -155,7 +155,6 @@ The following table illustrates the configuration properties used by the Tracker
|
||||
|shard.method|"DB_ID"|Data (Documents, ACLs) Routing criteria among shards| | |Y|Y| | |
|
||||
|alfresco.fingerprint|true|true if we want to compute the content Fingerprint| | |Y|| | |
|
||||
|alfresco.index.transformContent|true| | | |Y|| | |
|
||||
|alfresco.version|5.0.0|The target Alfresco version| | | | | | |
|
||||
|alfresco.corePoolSize|4|The number of threads to keep in the pool, even if they are idle|Y|Y|Y|Y|Y|Y|
|
||||
|alfresco.maximumPoolSize|-1|The maximum number of threads allowed in the pool|Y|Y|Y|Y|Y|Y|
|
||||
|alfresco.keepAliveTime|120|When the number of threads is greater than the core pool size, this is the maximum time that excess idle threads will wait for new tasks before terminating|Y|Y|Y|Y|Y|Y|
|
||||
|
||||
@@ -96,6 +96,57 @@
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-core</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-bindings-soap</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-bindings-xml</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-databinding-jaxb</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-frontend-jaxws</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-frontend-simple</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-transports-http</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-ws-addr</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-ws-policy</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-wsdl</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
@@ -107,7 +158,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -120,7 +171,7 @@
|
||||
<dependency>
|
||||
<groupId>com.carrotsearch.randomizedtesting</groupId>
|
||||
<artifactId>randomizedtesting-runner</artifactId>
|
||||
<version>2.7.4</version>
|
||||
<version>2.7.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -179,6 +230,45 @@
|
||||
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
|
||||
<excludes>
|
||||
<exclude>solrconfig.xml</exclude>
|
||||
<exclude>solrcore.properties</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-production-solr-configuration-for-master</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.testOutputDirectory}/test-files/master/conf</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
|
||||
<excludes>
|
||||
<exclude>solrconfig.xml</exclude>
|
||||
<exclude>solrcore.properties</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-production-solr-configuration-for-slave</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.testOutputDirectory}/test-files/slave/conf</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
|
||||
<excludes>
|
||||
<exclude>solrconfig.xml</exclude>
|
||||
<exclude>solrcore.properties</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
+26
-23
@@ -39,12 +39,9 @@ import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* @author Andy
|
||||
*
|
||||
*/
|
||||
public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
{
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.solr.schema.StrField#getSortField(org.apache.solr.schema.SchemaField, boolean)
|
||||
*/
|
||||
@@ -75,7 +72,6 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class MLTextSortFieldComparatorSource extends FieldComparatorSource
|
||||
{
|
||||
|
||||
@@ -101,16 +97,20 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
private final String[] values;
|
||||
|
||||
private BinaryDocValues docTerms;
|
||||
|
||||
private Bits docsWithField;
|
||||
|
||||
/**
|
||||
* An array of flags - one for each document in the segment. Each bit is set to true if the document has the
|
||||
* field or false otherwise. If this is set to null then all docs in the segment have the field.
|
||||
*/
|
||||
Bits docsWithField;
|
||||
|
||||
private final String field;
|
||||
|
||||
final Collator collator;
|
||||
Collator collator;
|
||||
|
||||
private String bottom;
|
||||
|
||||
private String top;
|
||||
String bottom;
|
||||
|
||||
String top;
|
||||
|
||||
Locale collatorLocale;
|
||||
|
||||
@@ -138,7 +138,7 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
{
|
||||
final String comparableString = findBestValue(doc, docTerms.get(doc));
|
||||
return compareValues(bottom, comparableString);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void copy(int slot, int doc)
|
||||
@@ -153,13 +153,14 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
|
||||
private String findBestValue(int doc, BytesRef term)
|
||||
{
|
||||
if (term.length == 0 && docsWithField.get(doc) == false) {
|
||||
if (term.length == 0 && docsWithField != null && docsWithField.get(doc) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
String withLocale = term.utf8ToString();
|
||||
|
||||
// split strin into MLText object
|
||||
|
||||
// split string into MLText object
|
||||
if (withLocale == null)
|
||||
{
|
||||
return withLocale;
|
||||
@@ -231,14 +232,15 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
{
|
||||
docTerms = DocValues.getBinary(context.reader(), field);
|
||||
docsWithField = DocValues.getDocsWithField(context.reader(), field);
|
||||
if (docsWithField instanceof Bits.MatchAllBits) {
|
||||
docsWithField = null;
|
||||
if (docsWithField instanceof Bits.MatchAllBits)
|
||||
{
|
||||
docsWithField = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareValues(String val1, String val2)
|
||||
public int compareValues(String val1, String val2)
|
||||
{
|
||||
if (val1 == null)
|
||||
{
|
||||
@@ -254,9 +256,10 @@ public class AlfrescoCollatableMLTextFieldType extends StrField
|
||||
}
|
||||
return collator.compare(val1, val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScorer(Scorer scorer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -104,16 +104,20 @@ public class AlfrescoCollatableTextFieldType extends StrField
|
||||
private final String[] values;
|
||||
|
||||
private BinaryDocValues docTerms;
|
||||
|
||||
private Bits docsWithField;
|
||||
|
||||
/**
|
||||
* An array of flags - one for each document in the segment. Each bit is set to true if the document has the
|
||||
* field or false otherwise. If this is set to null then all docs in the segment have the field.
|
||||
*/
|
||||
Bits docsWithField;
|
||||
|
||||
private final String field;
|
||||
|
||||
final Collator collator;
|
||||
Collator collator;
|
||||
|
||||
private String bottom;
|
||||
String bottom;
|
||||
|
||||
private String top;
|
||||
String top;
|
||||
|
||||
Locale collatorLocale;
|
||||
|
||||
@@ -141,7 +145,6 @@ public class AlfrescoCollatableTextFieldType extends StrField
|
||||
{
|
||||
final String comparableString = findBestValue(doc, docTerms.get(doc));
|
||||
return compareValues(bottom, comparableString);
|
||||
|
||||
}
|
||||
|
||||
public void copy(int slot, int doc)
|
||||
@@ -156,7 +159,8 @@ public class AlfrescoCollatableTextFieldType extends StrField
|
||||
|
||||
private String findBestValue(int doc, BytesRef term)
|
||||
{
|
||||
if (term.length == 0 && docsWithField.get(doc) == false) {
|
||||
if (term.length == 0 && docsWithField != null && docsWithField.get(doc) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+773
-719
File diff suppressed because it is too large
Load Diff
-484
@@ -1,484 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr;
|
||||
|
||||
import org.alfresco.httpclient.AuthenticationException;
|
||||
import org.alfresco.service.cmr.repository.datatype.Duration;
|
||||
import org.alfresco.solr.client.Node;
|
||||
import org.alfresco.solr.tracker.*;
|
||||
import org.alfresco.util.CachingDateFormat;
|
||||
import org.apache.commons.codec.EncoderException;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Methods taken from AlfrescoCoreAdminHandler that deal with building reports
|
||||
*/
|
||||
public class HandlerReportBuilder {
|
||||
|
||||
/**
|
||||
* Builds AclReport
|
||||
* @param tracker
|
||||
* @param aclid
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static NamedList<Object> buildAclReport(AclTracker tracker, Long aclid) throws IOException, JSONException
|
||||
{
|
||||
AclReport aclReport = tracker.checkAcl(aclid);
|
||||
|
||||
NamedList<Object> nr = new SimpleOrderedMap<Object>();
|
||||
nr.add("Acl Id", aclReport.getAclId());
|
||||
nr.add("Acl doc in index", aclReport.getIndexAclDoc());
|
||||
if (aclReport.getIndexAclDoc() != null)
|
||||
{
|
||||
nr.add("Acl tx in Index", aclReport.getIndexAclTx());
|
||||
}
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds TxReport
|
||||
* @param trackerRegistry
|
||||
* @param srv
|
||||
* @param coreName
|
||||
* @param tracker
|
||||
* @param txid
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public static NamedList<Object> buildTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, MetadataTracker tracker, Long txid)
|
||||
throws AuthenticationException, IOException, JSONException, EncoderException
|
||||
{
|
||||
NamedList<Object> nr = new SimpleOrderedMap<Object>();
|
||||
nr.add("TXID", txid);
|
||||
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, txid, txid, 0l, 0l, null, null));
|
||||
NamedList<Object> nodes = new SimpleOrderedMap<Object>();
|
||||
// add node reports ....
|
||||
List<Node> dbNodes = tracker.getFullNodesForDbTransaction(txid);
|
||||
for (Node node : dbNodes)
|
||||
{
|
||||
nodes.add("DBID " + node.getId(), buildNodeReport(tracker, node));
|
||||
}
|
||||
|
||||
nr.add("txDbNodeCount", dbNodes.size());
|
||||
nr.add("nodes", nodes);
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds AclTxReport
|
||||
* @param trackerRegistry
|
||||
* @param srv
|
||||
* @param coreName
|
||||
* @param tracker
|
||||
* @param acltxid
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public static NamedList<Object> buildAclTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, AclTracker tracker, Long acltxid)
|
||||
throws AuthenticationException, IOException, JSONException, EncoderException
|
||||
{
|
||||
NamedList<Object> nr = new SimpleOrderedMap<Object>();
|
||||
nr.add("TXID", acltxid);
|
||||
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, 0l, 0l, acltxid, acltxid, null, null));
|
||||
NamedList<Object> nodes = new SimpleOrderedMap<Object>();
|
||||
// add node reports ....
|
||||
List<Long> dbAclIds = tracker.getAclsForDbAclTransaction(acltxid);
|
||||
for (Long aclid : dbAclIds)
|
||||
{
|
||||
nodes.add("ACLID " + aclid, buildAclReport(tracker, aclid));
|
||||
}
|
||||
nr.add("aclTxDbAclCount", dbAclIds.size());
|
||||
nr.add("nodes", nodes);
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Node report
|
||||
* @param tracker
|
||||
* @param node
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static NamedList<Object> buildNodeReport(MetadataTracker tracker, Node node) throws IOException, JSONException
|
||||
{
|
||||
NodeReport nodeReport = tracker.checkNode(node);
|
||||
|
||||
NamedList<Object> nr = new SimpleOrderedMap<Object>();
|
||||
nr.add("Node DBID", nodeReport.getDbid());
|
||||
nr.add("DB TX", nodeReport.getDbTx());
|
||||
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
|
||||
if (nodeReport.getIndexLeafDoc() != null)
|
||||
{
|
||||
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
|
||||
}
|
||||
if (nodeReport.getIndexAuxDoc() != null)
|
||||
{
|
||||
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
|
||||
}
|
||||
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Node Report
|
||||
* @param tracker
|
||||
* @param dbid
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static NamedList<Object> buildNodeReport(MetadataTracker tracker, Long dbid) throws IOException, JSONException
|
||||
{
|
||||
NodeReport nodeReport = tracker.checkNode(dbid);
|
||||
|
||||
NamedList<Object> nr = new SimpleOrderedMap<Object>();
|
||||
nr.add("Node DBID", nodeReport.getDbid());
|
||||
nr.add("DB TX", nodeReport.getDbTx());
|
||||
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
|
||||
if (nodeReport.getIndexLeafDoc() != null)
|
||||
{
|
||||
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
|
||||
}
|
||||
if (nodeReport.getIndexAuxDoc() != null)
|
||||
{
|
||||
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
|
||||
}
|
||||
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Tracker report
|
||||
* @param trackerRegistry
|
||||
* @param srv
|
||||
* @param coreName
|
||||
* @param fromTx
|
||||
* @param toTx
|
||||
* @param fromAclTx
|
||||
* @param toAclTx
|
||||
* @param fromTime
|
||||
* @param toTime
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
* @throws AuthenticationException
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public static NamedList<Object> buildTrackerReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, Long fromTx, Long toTx, Long fromAclTx, Long toAclTx,
|
||||
Long fromTime, Long toTime) throws IOException, JSONException, AuthenticationException, EncoderException
|
||||
{
|
||||
// ACL
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
IndexHealthReport aclReport = aclTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
|
||||
NamedList<Object> ihr = new SimpleOrderedMap<Object>();
|
||||
ihr.add("Alfresco version", aclTracker.getAlfrescoVersion());
|
||||
ihr.add("DB acl transaction count", aclReport.getDbAclTransactionCount());
|
||||
ihr.add("Count of duplicated acl transactions in the index", aclReport.getDuplicatedAclTxInIndex()
|
||||
.cardinality());
|
||||
if (aclReport.getDuplicatedAclTxInIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First duplicate acl tx", aclReport.getDuplicatedAclTxInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of acl transactions in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
|
||||
.cardinality());
|
||||
if (aclReport.getAclTxInIndexButNotInDb().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First acl transaction in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of missing acl transactions from the Index", aclReport.getMissingAclTxFromIndex()
|
||||
.cardinality());
|
||||
if (aclReport.getMissingAclTxFromIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First acl transaction missing from the Index", aclReport.getMissingAclTxFromIndex()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index acl transaction count", aclReport.getAclTransactionDocsInIndex());
|
||||
ihr.add("Index unique acl transaction count", aclReport.getAclTransactionDocsInIndex());
|
||||
TrackerState aclState = aclTracker.getTrackerState();
|
||||
ihr.add("Last indexed change set commit time", aclState.getLastIndexedChangeSetCommitTime());
|
||||
Date lastChangeSetDate = new Date(aclState.getLastIndexedChangeSetCommitTime());
|
||||
ihr.add("Last indexed change set commit date", CachingDateFormat.getDateFormat().format(lastChangeSetDate));
|
||||
ihr.add("Last changeset id before holes", aclState.getLastIndexedChangeSetIdBeforeHoles());
|
||||
|
||||
// Metadata
|
||||
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
IndexHealthReport metaReport = metadataTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
|
||||
ihr.add("DB transaction count", metaReport.getDbTransactionCount());
|
||||
ihr.add("Count of duplicated transactions in the index", metaReport.getDuplicatedTxInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedTxInIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First duplicate", metaReport.getDuplicatedTxInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of transactions in the index but not the DB", metaReport.getTxInIndexButNotInDb()
|
||||
.cardinality());
|
||||
if (metaReport.getTxInIndexButNotInDb().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First transaction in the index but not the DB", metaReport.getTxInIndexButNotInDb()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of missing transactions from the Index", metaReport.getMissingTxFromIndex().cardinality());
|
||||
if (metaReport.getMissingTxFromIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First transaction missing from the Index", metaReport.getMissingTxFromIndex()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index transaction count", metaReport.getTransactionDocsInIndex());
|
||||
ihr.add("Index unique transaction count", metaReport.getTransactionDocsInIndex());
|
||||
ihr.add("Index node count", metaReport.getLeafDocCountInIndex());
|
||||
ihr.add("Count of duplicate nodes in the index", metaReport.getDuplicatedLeafInIndex().cardinality());
|
||||
if (metaReport.getDuplicatedLeafInIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First duplicate node id in the index", metaReport.getDuplicatedLeafInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index error count", metaReport.getErrorDocCountInIndex());
|
||||
ihr.add("Count of duplicate error docs in the index", metaReport.getDuplicatedErrorInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedErrorInIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First duplicate error in the index", SolrInformationServer.PREFIX_ERROR
|
||||
+ metaReport.getDuplicatedErrorInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index unindexed count", metaReport.getUnindexedDocCountInIndex());
|
||||
ihr.add("Count of duplicate unindexed docs in the index", metaReport.getDuplicatedUnindexedInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedUnindexedInIndex().cardinality() > 0)
|
||||
{
|
||||
ihr.add("First duplicate unindexed in the index",
|
||||
metaReport.getDuplicatedUnindexedInIndex().nextSetBit(0L));
|
||||
}
|
||||
TrackerState metaState = metadataTracker.getTrackerState();
|
||||
ihr.add("Last indexed transaction commit time", metaState.getLastIndexedTxCommitTime());
|
||||
Date lastTxDate = new Date(metaState.getLastIndexedTxCommitTime());
|
||||
ihr.add("Last indexed transaction commit date", CachingDateFormat.getDateFormat().format(lastTxDate));
|
||||
ihr.add("Last TX id before holes", metaState.getLastIndexedTxIdBeforeHoles());
|
||||
|
||||
srv.addFTSStatusCounts(ihr);
|
||||
|
||||
return ihr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a core summary
|
||||
* @param cname
|
||||
* @param detail
|
||||
* @param hist
|
||||
* @param values
|
||||
* @param srv
|
||||
* @param report
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void addCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
|
||||
InformationServer srv, NamedList<Object> report) throws IOException
|
||||
{
|
||||
NamedList<Object> coreSummary = new SimpleOrderedMap<Object>();
|
||||
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
|
||||
|
||||
MetadataTracker metaTrkr = trackerRegistry.getTrackerForCore(cname, MetadataTracker.class);
|
||||
TrackerState metadataTrkrState = metaTrkr.getTrackerState();
|
||||
long lastIndexTxCommitTime = metadataTrkrState.getLastIndexedTxCommitTime();
|
||||
|
||||
long lastIndexedTxId = metadataTrkrState.getLastIndexedTxId();
|
||||
long lastTxCommitTimeOnServer = metadataTrkrState.getLastTxCommitTimeOnServer();
|
||||
long lastTxIdOnServer = metadataTrkrState.getLastTxIdOnServer();
|
||||
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
|
||||
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
|
||||
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
|
||||
if (transactionsToDo < 0)
|
||||
{
|
||||
transactionsToDo = 0;
|
||||
}
|
||||
|
||||
AclTracker aclTrkr = trackerRegistry.getTrackerForCore(cname, AclTracker.class);
|
||||
TrackerState aclTrkrState = aclTrkr.getTrackerState();
|
||||
long lastIndexChangeSetCommitTime = aclTrkrState.getLastIndexedChangeSetCommitTime();
|
||||
long lastIndexedChangeSetId = aclTrkrState.getLastIndexedChangeSetId();
|
||||
long lastChangeSetCommitTimeOnServer = aclTrkrState.getLastChangeSetCommitTimeOnServer();
|
||||
long lastChangeSetIdOnServer = aclTrkrState.getLastChangeSetIdOnServer();
|
||||
Date lastIndexChangeSetCommitDate = new Date(lastIndexChangeSetCommitTime);
|
||||
Date lastChangeSetOnServerDate = new Date(lastChangeSetCommitTimeOnServer);
|
||||
long changeSetsToDo = lastChangeSetIdOnServer - lastIndexedChangeSetId;
|
||||
if (changeSetsToDo < 0)
|
||||
{
|
||||
changeSetsToDo = 0;
|
||||
}
|
||||
|
||||
long nodesToDo = 0;
|
||||
long remainingTxTimeMillis = 0;
|
||||
if (transactionsToDo > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out metadata indexing
|
||||
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
|
||||
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
|
||||
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
|
||||
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
|
||||
}
|
||||
Date now = new Date();
|
||||
Date end = new Date(now.getTime() + remainingTxTimeMillis);
|
||||
Duration remainingTx = new Duration(now, end);
|
||||
|
||||
long remainingChangeSetTimeMillis = 0;
|
||||
if (changeSetsToDo > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanAclsPerChangeSet = srv.getTrackerStats().getMeanAclsPerChangeSet();
|
||||
double meanAclElapsedIndexTime = srv.getTrackerStats().getMeanAclElapsedIndexTime();
|
||||
remainingChangeSetTimeMillis = (long) (changeSetsToDo * meanAclsPerChangeSet * meanAclElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingChangeSetTimeMillis);
|
||||
Duration remainingChangeSet = new Duration(now, end);
|
||||
|
||||
NamedList<Object> ftsSummary = new SimpleOrderedMap<Object>();
|
||||
long remainingContentTimeMillis = 0;
|
||||
srv.addFTSStatusCounts(ftsSummary);
|
||||
long cleanCount = ((Long)ftsSummary.get("Node count with FTSStatus Clean")).longValue();
|
||||
long dirtyCount = ((Long)ftsSummary.get("Node count with FTSStatus Dirty")).longValue();
|
||||
long newCount = ((Long)ftsSummary.get("Node count with FTSStatus New")).longValue();
|
||||
long nodesInIndex = ((Long)coreSummary.get("Alfresco Nodes in Index"));
|
||||
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;;
|
||||
if (dirtyCount + newCount + contentYetToSee > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
|
||||
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingContentTimeMillis);
|
||||
Duration remainingContent = new Duration(now, end);
|
||||
coreSummary.add("FTS",ftsSummary);
|
||||
|
||||
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
|
||||
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
|
||||
{
|
||||
txLag = new Duration();
|
||||
}
|
||||
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
|
||||
if (txLagSeconds < 0)
|
||||
{
|
||||
txLagSeconds = 0;
|
||||
}
|
||||
|
||||
Duration changeSetLag = new Duration(lastIndexChangeSetCommitDate, lastChangeSetOnServerDate);
|
||||
if (lastIndexChangeSetCommitDate.compareTo(lastChangeSetOnServerDate) > 0)
|
||||
{
|
||||
changeSetLag = new Duration();
|
||||
}
|
||||
long changeSetLagSeconds = (lastChangeSetCommitTimeOnServer - lastIndexChangeSetCommitTime) / 1000;
|
||||
if (txLagSeconds < 0)
|
||||
{
|
||||
txLagSeconds = 0;
|
||||
}
|
||||
|
||||
ContentTracker contentTrkr = trackerRegistry.getTrackerForCore(cname, ContentTracker.class);
|
||||
TrackerState contentTrkrState = contentTrkr.getTrackerState();
|
||||
// Leave ModelTracker out of this check, because it is common
|
||||
boolean aTrackerIsRunning = aclTrkrState.isRunning() || metadataTrkrState.isRunning()
|
||||
|| contentTrkrState.isRunning();
|
||||
coreSummary.add("Active", aTrackerIsRunning);
|
||||
|
||||
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
|
||||
TrackerState modelTrkrState = modelTrkr.getTrackerState();
|
||||
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
|
||||
coreSummary.add("ContentTracker Active", contentTrkrState.isRunning());
|
||||
coreSummary.add("MetadataTracker Active", metadataTrkrState.isRunning());
|
||||
coreSummary.add("AclTracker Active", aclTrkrState.isRunning());
|
||||
|
||||
// TX
|
||||
|
||||
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
|
||||
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
|
||||
coreSummary.add("TX Lag", txLagSeconds + " s");
|
||||
coreSummary.add("TX Duration", txLag.toString());
|
||||
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
|
||||
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
|
||||
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
|
||||
coreSummary.add("Id for last TX in index", lastIndexedTxId);
|
||||
coreSummary.add("Approx transactions remaining", transactionsToDo);
|
||||
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
|
||||
|
||||
// Change set
|
||||
|
||||
coreSummary.add("Last Index Change Set Commit Time", lastIndexChangeSetCommitTime);
|
||||
coreSummary.add("Last Index Change Set Commit Date", lastIndexChangeSetCommitDate);
|
||||
coreSummary.add("Change Set Lag", changeSetLagSeconds + " s");
|
||||
coreSummary.add("Change Set Duration", changeSetLag.toString());
|
||||
coreSummary.add("Timestamp for last Change Set on server", lastChangeSetCommitTimeOnServer);
|
||||
coreSummary.add("Date for last Change Set on server", lastChangeSetOnServerDate);
|
||||
coreSummary.add("Id for last Change Set on server", lastChangeSetIdOnServer);
|
||||
coreSummary.add("Id for last Change Set in index", lastIndexedChangeSetId);
|
||||
coreSummary.add("Approx change sets remaining", changeSetsToDo);
|
||||
coreSummary.add("Approx change set indexing time remaining",
|
||||
remainingChangeSet.largestComponentformattedString());
|
||||
|
||||
coreSummary.add("Approx content indexing time remaining",
|
||||
remainingContent.largestComponentformattedString());
|
||||
|
||||
// Stats
|
||||
|
||||
coreSummary.add("Model sync times (ms)",
|
||||
srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Acl index time (ms)",
|
||||
srv.getTrackerStats().getAclTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Node index time (ms)",
|
||||
srv.getTrackerStats().getNodeTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Doc Transformation time (ms)", srv.getTrackerStats().getDocTransformationTimes()
|
||||
.getNamedList(detail, hist, values));
|
||||
|
||||
// Model
|
||||
|
||||
Map<String, Set<String>> modelErrors = srv.getModelErrors();
|
||||
if (modelErrors.size() > 0)
|
||||
{
|
||||
NamedList<Object> errorList = new SimpleOrderedMap<Object>();
|
||||
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
|
||||
{
|
||||
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
|
||||
}
|
||||
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied",
|
||||
errorList);
|
||||
}
|
||||
|
||||
report.add(cname, coreSummary);
|
||||
}
|
||||
}
|
||||
+554
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.service.cmr.repository.datatype.Duration;
|
||||
import org.alfresco.solr.client.Node;
|
||||
import org.alfresco.solr.tracker.*;
|
||||
import org.alfresco.util.CachingDateFormat;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
/**
|
||||
* Methods taken from AlfrescoCoreAdminHandler that deal with building reports
|
||||
*/
|
||||
class HandlerReportHelper
|
||||
{
|
||||
static NamedList<Object> buildAclReport(AclTracker tracker, Long aclid) throws JSONException
|
||||
{
|
||||
AclReport aclReport = tracker.checkAcl(aclid);
|
||||
|
||||
NamedList<Object> nr = new SimpleOrderedMap<>();
|
||||
nr.add("Acl Id", aclReport.getAclId());
|
||||
nr.add("Acl doc in index", aclReport.getIndexAclDoc());
|
||||
if (aclReport.getIndexAclDoc() != null)
|
||||
{
|
||||
nr.add("Acl tx in Index", aclReport.getIndexAclTx());
|
||||
}
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
static NamedList<Object> buildTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, MetadataTracker tracker, Long txid) throws JSONException
|
||||
{
|
||||
NamedList<Object> nr = new SimpleOrderedMap<>();
|
||||
nr.add("TXID", txid);
|
||||
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, txid, txid, 0L, 0L, null, null));
|
||||
NamedList<Object> nodes = new SimpleOrderedMap<>();
|
||||
|
||||
// add node reports ....
|
||||
List<Node> dbNodes = tracker.getFullNodesForDbTransaction(txid);
|
||||
for (Node node : dbNodes)
|
||||
{
|
||||
nodes.add("DBID " + node.getId(), buildNodeReport(tracker, node));
|
||||
}
|
||||
|
||||
nr.add("txDbNodeCount", dbNodes.size());
|
||||
nr.add("nodes", nodes);
|
||||
return nr;
|
||||
}
|
||||
|
||||
static NamedList<Object> buildAclTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, AclTracker tracker, Long acltxid) throws JSONException
|
||||
{
|
||||
try {
|
||||
NamedList<Object> nr = new SimpleOrderedMap<>();
|
||||
nr.add("TXID", acltxid);
|
||||
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, 0L, 0L, acltxid, acltxid, null, null));
|
||||
NamedList<Object> nodes = new SimpleOrderedMap<>();
|
||||
|
||||
// add node reports ....
|
||||
List<Long> dbAclIds = tracker.getAclsForDbAclTransaction(acltxid);
|
||||
for (Long aclid : dbAclIds) {
|
||||
nodes.add("ACLID " + aclid, buildAclReport(tracker, aclid));
|
||||
}
|
||||
nr.add("aclTxDbAclCount", dbAclIds.size());
|
||||
nr.add("nodes", nodes);
|
||||
return nr;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("", exception);
|
||||
}
|
||||
}
|
||||
|
||||
static NamedList<Object> buildNodeReport(MetadataTracker tracker, Node node) throws JSONException
|
||||
{
|
||||
NodeReport nodeReport = tracker.checkNode(node);
|
||||
|
||||
NamedList<Object> nr = new SimpleOrderedMap<>();
|
||||
nr.add("Node DBID", nodeReport.getDbid());
|
||||
nr.add("DB TX", nodeReport.getDbTx());
|
||||
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
|
||||
if (nodeReport.getIndexLeafDoc() != null)
|
||||
{
|
||||
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
|
||||
}
|
||||
if (nodeReport.getIndexAuxDoc() != null)
|
||||
{
|
||||
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
|
||||
}
|
||||
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
|
||||
return nr;
|
||||
}
|
||||
|
||||
static NamedList<Object> buildNodeReport(CoreStatePublisher publisher, Long dbid) throws JSONException
|
||||
{
|
||||
NodeReport nodeReport = publisher.checkNode(dbid);
|
||||
|
||||
NamedList<Object> payload = new SimpleOrderedMap<>();
|
||||
payload.add("Node DBID", nodeReport.getDbid());
|
||||
|
||||
if (publisher.isOnMasterOrStandalone())
|
||||
{
|
||||
ofNullable(nodeReport.getDbTx()).ifPresent(value -> payload.add("DB TX", value));
|
||||
ofNullable(nodeReport.getDbNodeStatus()).map(Object::toString).ifPresent(value -> payload.add("DB TX Status", value));
|
||||
ofNullable(nodeReport.getIndexLeafTx()).ifPresent(value -> payload.add("Leaf tx in Index", value));
|
||||
ofNullable(nodeReport.getIndexAuxDoc()).ifPresent(value -> payload.add("Aux tx in Index", value));
|
||||
}
|
||||
else
|
||||
{
|
||||
payload.add("WARNING", "This response comes from a slave core and it contains minimal information about the node. " +
|
||||
"Please consider to re-submit the same request to the corresponding Master, in order to get more information.");
|
||||
}
|
||||
|
||||
ofNullable(nodeReport.getIndexedNodeDocCount()).ifPresent(value -> payload.add("Indexed Node Doc Count", value));
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
static NamedList<Object> buildTrackerReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, Long fromTx, Long toTx, Long fromAclTx, Long toAclTx,
|
||||
Long fromTime, Long toTime) throws JSONException
|
||||
{
|
||||
try
|
||||
{
|
||||
// ACL
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
IndexHealthReport aclReport = aclTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
|
||||
NamedList<Object> ihr = new SimpleOrderedMap<>();
|
||||
ihr.add("DB acl transaction count", aclReport.getDbAclTransactionCount());
|
||||
ihr.add("Count of duplicated acl transactions in the index", aclReport.getDuplicatedAclTxInIndex()
|
||||
.cardinality());
|
||||
if (aclReport.getDuplicatedAclTxInIndex().cardinality() > 0) {
|
||||
ihr.add("First duplicate acl tx", aclReport.getDuplicatedAclTxInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of acl transactions in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
|
||||
.cardinality());
|
||||
if (aclReport.getAclTxInIndexButNotInDb().cardinality() > 0) {
|
||||
ihr.add("First acl transaction in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of missing acl transactions from the Index", aclReport.getMissingAclTxFromIndex()
|
||||
.cardinality());
|
||||
if (aclReport.getMissingAclTxFromIndex().cardinality() > 0) {
|
||||
ihr.add("First acl transaction missing from the Index", aclReport.getMissingAclTxFromIndex()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index acl transaction count", aclReport.getAclTransactionDocsInIndex());
|
||||
ihr.add("Index unique acl transaction count", aclReport.getAclTransactionDocsInIndex());
|
||||
TrackerState aclState = aclTracker.getTrackerState();
|
||||
ihr.add("Last indexed change set commit time", aclState.getLastIndexedChangeSetCommitTime());
|
||||
Date lastChangeSetDate = new Date(aclState.getLastIndexedChangeSetCommitTime());
|
||||
ihr.add("Last indexed change set commit date", CachingDateFormat.getDateFormat().format(lastChangeSetDate));
|
||||
ihr.add("Last changeset id before holes", aclState.getLastIndexedChangeSetIdBeforeHoles());
|
||||
|
||||
// Metadata
|
||||
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
IndexHealthReport metaReport = metadataTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
|
||||
ihr.add("DB transaction count", metaReport.getDbTransactionCount());
|
||||
ihr.add("Count of duplicated transactions in the index", metaReport.getDuplicatedTxInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedTxInIndex().cardinality() > 0) {
|
||||
ihr.add("First duplicate", metaReport.getDuplicatedTxInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of transactions in the index but not the DB", metaReport.getTxInIndexButNotInDb()
|
||||
.cardinality());
|
||||
if (metaReport.getTxInIndexButNotInDb().cardinality() > 0) {
|
||||
ihr.add("First transaction in the index but not the DB", metaReport.getTxInIndexButNotInDb()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Count of missing transactions from the Index", metaReport.getMissingTxFromIndex().cardinality());
|
||||
if (metaReport.getMissingTxFromIndex().cardinality() > 0) {
|
||||
ihr.add("First transaction missing from the Index", metaReport.getMissingTxFromIndex()
|
||||
.nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index transaction count", metaReport.getTransactionDocsInIndex());
|
||||
ihr.add("Index unique transaction count", metaReport.getTransactionDocsInIndex());
|
||||
ihr.add("Index node count", metaReport.getLeafDocCountInIndex());
|
||||
ihr.add("Count of duplicate nodes in the index", metaReport.getDuplicatedLeafInIndex().cardinality());
|
||||
if (metaReport.getDuplicatedLeafInIndex().cardinality() > 0) {
|
||||
ihr.add("First duplicate node id in the index", metaReport.getDuplicatedLeafInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index error count", metaReport.getErrorDocCountInIndex());
|
||||
ihr.add("Count of duplicate error docs in the index", metaReport.getDuplicatedErrorInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedErrorInIndex().cardinality() > 0) {
|
||||
ihr.add("First duplicate error in the index", SolrInformationServer.PREFIX_ERROR
|
||||
+ metaReport.getDuplicatedErrorInIndex().nextSetBit(0L));
|
||||
}
|
||||
ihr.add("Index unindexed count", metaReport.getUnindexedDocCountInIndex());
|
||||
ihr.add("Count of duplicate unindexed docs in the index", metaReport.getDuplicatedUnindexedInIndex()
|
||||
.cardinality());
|
||||
if (metaReport.getDuplicatedUnindexedInIndex().cardinality() > 0) {
|
||||
ihr.add("First duplicate unindexed in the index",
|
||||
metaReport.getDuplicatedUnindexedInIndex().nextSetBit(0L));
|
||||
}
|
||||
TrackerState metaState = metadataTracker.getTrackerState();
|
||||
ihr.add("Last indexed transaction commit time", metaState.getLastIndexedTxCommitTime());
|
||||
Date lastTxDate = new Date(metaState.getLastIndexedTxCommitTime());
|
||||
ihr.add("Last indexed transaction commit date", CachingDateFormat.getDateFormat().format(lastTxDate));
|
||||
ihr.add("Last TX id before holes", metaState.getLastIndexedTxIdBeforeHoles());
|
||||
|
||||
srv.addFTSStatusCounts(ihr);
|
||||
|
||||
return ihr;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("", exception);
|
||||
}
|
||||
}
|
||||
|
||||
static void addSlaveCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
|
||||
InformationServer srv, NamedList<Object> report) throws IOException
|
||||
{
|
||||
NamedList<Object> coreSummary = new SimpleOrderedMap<>();
|
||||
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
|
||||
|
||||
SlaveCoreStatePublisher statePublisher = trackerRegistry.getTrackerForCore(cname, SlaveCoreStatePublisher.class);
|
||||
TrackerState trackerState = statePublisher.getTrackerState();
|
||||
long lastIndexTxCommitTime = trackerState.getLastIndexedTxCommitTime();
|
||||
|
||||
long lastIndexedTxId = trackerState.getLastIndexedTxId();
|
||||
long lastTxCommitTimeOnServer = trackerState.getLastTxCommitTimeOnServer();
|
||||
long lastTxIdOnServer = trackerState.getLastTxIdOnServer();
|
||||
|
||||
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
|
||||
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
|
||||
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
|
||||
if (transactionsToDo < 0)
|
||||
{
|
||||
transactionsToDo = 0;
|
||||
}
|
||||
|
||||
long nodesToDo = 0;
|
||||
long remainingTxTimeMillis = 0;
|
||||
if (transactionsToDo > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out metadata indexing
|
||||
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
|
||||
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
|
||||
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
|
||||
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
|
||||
}
|
||||
Date now = new Date();
|
||||
Date end = new Date(now.getTime() + remainingTxTimeMillis);
|
||||
Duration remainingTx = new Duration(now, end);
|
||||
|
||||
NamedList<Object> ftsSummary = new SimpleOrderedMap<>();
|
||||
long remainingContentTimeMillis = 0;
|
||||
srv.addFTSStatusCounts(ftsSummary);
|
||||
long cleanCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Clean"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long dirtyCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Dirty"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long newCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus New"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
|
||||
long nodesInIndex =
|
||||
ofNullable(coreSummary.get("Alfresco Nodes in Index"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
|
||||
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;
|
||||
if (dirtyCount + newCount + contentYetToSee > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
|
||||
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingContentTimeMillis);
|
||||
Duration remainingContent = new Duration(now, end);
|
||||
coreSummary.add("FTS",ftsSummary);
|
||||
|
||||
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
|
||||
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
|
||||
{
|
||||
txLag = new Duration();
|
||||
}
|
||||
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
|
||||
if (txLagSeconds < 0)
|
||||
{
|
||||
txLagSeconds = 0;
|
||||
}
|
||||
|
||||
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
|
||||
TrackerState modelTrkrState = modelTrkr.getTrackerState();
|
||||
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
|
||||
coreSummary.add("NodeState Publisher Active", trackerState.isRunning());
|
||||
|
||||
// TX
|
||||
|
||||
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
|
||||
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
|
||||
coreSummary.add("TX Lag", txLagSeconds + " s");
|
||||
coreSummary.add("TX Duration", txLag.toString());
|
||||
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
|
||||
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
|
||||
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
|
||||
coreSummary.add("Id for last TX in index", lastIndexedTxId);
|
||||
coreSummary.add("Approx transactions remaining", transactionsToDo);
|
||||
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
|
||||
// Stats
|
||||
|
||||
coreSummary.add("Model sync times (ms)", srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
|
||||
|
||||
// Model
|
||||
|
||||
Map<String, Set<String>> modelErrors = srv.getModelErrors();
|
||||
if (modelErrors.size() > 0)
|
||||
{
|
||||
NamedList<Object> errorList = new SimpleOrderedMap<>();
|
||||
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
|
||||
{
|
||||
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
|
||||
}
|
||||
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied", errorList);
|
||||
}
|
||||
|
||||
report.add(cname, coreSummary);
|
||||
}
|
||||
|
||||
static void addMasterOrStandaloneCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
|
||||
InformationServer srv, NamedList<Object> report) throws IOException
|
||||
{
|
||||
NamedList<Object> coreSummary = new SimpleOrderedMap<>();
|
||||
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
|
||||
|
||||
MetadataTracker metaTrkr = trackerRegistry.getTrackerForCore(cname, MetadataTracker.class);
|
||||
TrackerState metadataTrkrState = metaTrkr.getTrackerState();
|
||||
long lastIndexTxCommitTime = metadataTrkrState.getLastIndexedTxCommitTime();
|
||||
|
||||
long lastIndexedTxId = metadataTrkrState.getLastIndexedTxId();
|
||||
long lastTxCommitTimeOnServer = metadataTrkrState.getLastTxCommitTimeOnServer();
|
||||
long lastTxIdOnServer = metadataTrkrState.getLastTxIdOnServer();
|
||||
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
|
||||
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
|
||||
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
|
||||
if (transactionsToDo < 0)
|
||||
{
|
||||
transactionsToDo = 0;
|
||||
}
|
||||
|
||||
AclTracker aclTrkr = trackerRegistry.getTrackerForCore(cname, AclTracker.class);
|
||||
TrackerState aclTrkrState = aclTrkr.getTrackerState();
|
||||
long lastIndexChangeSetCommitTime = aclTrkrState.getLastIndexedChangeSetCommitTime();
|
||||
long lastIndexedChangeSetId = aclTrkrState.getLastIndexedChangeSetId();
|
||||
long lastChangeSetCommitTimeOnServer = aclTrkrState.getLastChangeSetCommitTimeOnServer();
|
||||
long lastChangeSetIdOnServer = aclTrkrState.getLastChangeSetIdOnServer();
|
||||
Date lastIndexChangeSetCommitDate = new Date(lastIndexChangeSetCommitTime);
|
||||
Date lastChangeSetOnServerDate = new Date(lastChangeSetCommitTimeOnServer);
|
||||
long changeSetsToDo = lastChangeSetIdOnServer - lastIndexedChangeSetId;
|
||||
if (changeSetsToDo < 0)
|
||||
{
|
||||
changeSetsToDo = 0;
|
||||
}
|
||||
|
||||
long nodesToDo = 0;
|
||||
long remainingTxTimeMillis = 0;
|
||||
if (transactionsToDo > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out metadata indexing
|
||||
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
|
||||
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
|
||||
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
|
||||
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
|
||||
}
|
||||
Date now = new Date();
|
||||
Date end = new Date(now.getTime() + remainingTxTimeMillis);
|
||||
Duration remainingTx = new Duration(now, end);
|
||||
|
||||
long remainingChangeSetTimeMillis = 0;
|
||||
if (changeSetsToDo > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanAclsPerChangeSet = srv.getTrackerStats().getMeanAclsPerChangeSet();
|
||||
double meanAclElapsedIndexTime = srv.getTrackerStats().getMeanAclElapsedIndexTime();
|
||||
remainingChangeSetTimeMillis = (long) (changeSetsToDo * meanAclsPerChangeSet * meanAclElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingChangeSetTimeMillis);
|
||||
Duration remainingChangeSet = new Duration(now, end);
|
||||
|
||||
NamedList<Object> ftsSummary = new SimpleOrderedMap<>();
|
||||
long remainingContentTimeMillis = 0;
|
||||
srv.addFTSStatusCounts(ftsSummary);
|
||||
long cleanCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Clean"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long dirtyCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Dirty"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long newCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus New"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
|
||||
long nodesInIndex =
|
||||
ofNullable(coreSummary.get("Alfresco Nodes in Index"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
|
||||
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;
|
||||
if (dirtyCount + newCount + contentYetToSee > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
|
||||
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingContentTimeMillis);
|
||||
Duration remainingContent = new Duration(now, end);
|
||||
coreSummary.add("FTS",ftsSummary);
|
||||
|
||||
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
|
||||
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
|
||||
{
|
||||
txLag = new Duration();
|
||||
}
|
||||
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
|
||||
if (txLagSeconds < 0)
|
||||
{
|
||||
txLagSeconds = 0;
|
||||
}
|
||||
|
||||
Duration changeSetLag = new Duration(lastIndexChangeSetCommitDate, lastChangeSetOnServerDate);
|
||||
if (lastIndexChangeSetCommitDate.compareTo(lastChangeSetOnServerDate) > 0)
|
||||
{
|
||||
changeSetLag = new Duration();
|
||||
}
|
||||
long changeSetLagSeconds = (lastChangeSetCommitTimeOnServer - lastIndexChangeSetCommitTime) / 1000;
|
||||
if (txLagSeconds < 0)
|
||||
{
|
||||
txLagSeconds = 0;
|
||||
}
|
||||
|
||||
ContentTracker contentTrkr = trackerRegistry.getTrackerForCore(cname, ContentTracker.class);
|
||||
TrackerState contentTrkrState = contentTrkr.getTrackerState();
|
||||
// Leave ModelTracker out of this check, because it is common
|
||||
boolean aTrackerIsRunning = aclTrkrState.isRunning() || metadataTrkrState.isRunning()
|
||||
|| contentTrkrState.isRunning();
|
||||
coreSummary.add("Active", aTrackerIsRunning);
|
||||
|
||||
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
|
||||
TrackerState modelTrkrState = modelTrkr.getTrackerState();
|
||||
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
|
||||
coreSummary.add("ContentTracker Active", contentTrkrState.isRunning());
|
||||
coreSummary.add("MetadataTracker Active", metadataTrkrState.isRunning());
|
||||
coreSummary.add("AclTracker Active", aclTrkrState.isRunning());
|
||||
|
||||
// TX
|
||||
|
||||
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
|
||||
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
|
||||
coreSummary.add("TX Lag", txLagSeconds + " s");
|
||||
coreSummary.add("TX Duration", txLag.toString());
|
||||
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
|
||||
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
|
||||
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
|
||||
coreSummary.add("Id for last TX in index", lastIndexedTxId);
|
||||
coreSummary.add("Approx transactions remaining", transactionsToDo);
|
||||
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
|
||||
|
||||
// Change set
|
||||
|
||||
coreSummary.add("Last Index Change Set Commit Time", lastIndexChangeSetCommitTime);
|
||||
coreSummary.add("Last Index Change Set Commit Date", lastIndexChangeSetCommitDate);
|
||||
coreSummary.add("Change Set Lag", changeSetLagSeconds + " s");
|
||||
coreSummary.add("Change Set Duration", changeSetLag.toString());
|
||||
coreSummary.add("Timestamp for last Change Set on server", lastChangeSetCommitTimeOnServer);
|
||||
coreSummary.add("Date for last Change Set on server", lastChangeSetOnServerDate);
|
||||
coreSummary.add("Id for last Change Set on server", lastChangeSetIdOnServer);
|
||||
coreSummary.add("Id for last Change Set in index", lastIndexedChangeSetId);
|
||||
coreSummary.add("Approx change sets remaining", changeSetsToDo);
|
||||
coreSummary.add("Approx change set indexing time remaining",
|
||||
remainingChangeSet.largestComponentformattedString());
|
||||
|
||||
coreSummary.add("Approx content indexing time remaining",
|
||||
remainingContent.largestComponentformattedString());
|
||||
|
||||
// Stats
|
||||
|
||||
coreSummary.add("Model sync times (ms)",
|
||||
srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Acl index time (ms)",
|
||||
srv.getTrackerStats().getAclTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Node index time (ms)",
|
||||
srv.getTrackerStats().getNodeTimes().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
|
||||
coreSummary.add("Doc Transformation time (ms)", srv.getTrackerStats().getDocTransformationTimes()
|
||||
.getNamedList(detail, hist, values));
|
||||
|
||||
// Model
|
||||
|
||||
Map<String, Set<String>> modelErrors = srv.getModelErrors();
|
||||
if (modelErrors.size() > 0)
|
||||
{
|
||||
NamedList<Object> errorList = new SimpleOrderedMap<>();
|
||||
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
|
||||
{
|
||||
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
|
||||
}
|
||||
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied",
|
||||
errorList);
|
||||
}
|
||||
|
||||
report.add(cname, coreSummary);
|
||||
}
|
||||
}
|
||||
@@ -179,4 +179,6 @@ public interface InformationServer extends InformationServerCollectionProvider
|
||||
String getHostName();
|
||||
|
||||
String getBaseUrl();
|
||||
|
||||
void flushContentStore() throws IOException;
|
||||
}
|
||||
|
||||
+41
-36
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Alfresco Software Limited.
|
||||
* Copyright (C) 2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -178,14 +178,14 @@ import org.springframework.extensions.surf.util.I18NUtil;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* This is the Solr4 implementation of the information server (index).
|
||||
* This is the Apache Solr implementation of the information server (index).
|
||||
*
|
||||
* @author Ahmed Owian
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SolrInformationServer implements InformationServer
|
||||
{
|
||||
private final static Log LOGGER = new Log(SolrInformationServer.class);
|
||||
private final static long TWO_MINUTES = 120000;
|
||||
|
||||
private static final String NO_SITE = "_REPOSITORY_";
|
||||
private static final String SHARED_FILES = "_SHARED_FILES_";
|
||||
@@ -200,23 +200,23 @@ public class SolrInformationServer implements InformationServer
|
||||
|
||||
public static final String AND = " AND ";
|
||||
public static final String OR = " OR ";
|
||||
public static final String REQUEST_HANDLER_ALFRESCO_FULL_TEXT_SEARCH = "/afts";
|
||||
public static final String REQUEST_HANDLER_NATIVE = "/native";
|
||||
public static final String REQUEST_HANDLER_ALFRESCO = "/alfresco";
|
||||
public static final String REQUEST_HANDLER_SELECT = "/select";
|
||||
public static final String REQUEST_HANDLER_GET = "/get";
|
||||
public static final String RESPONSE_DEFAULT_IDS = "response";
|
||||
public static final String RESPONSE_DEFAULT_ID = "doc";
|
||||
//public static final String REQUEST_HANDLER_ALFRESCO_FULL_TEXT_SEARCH = "/afts";
|
||||
private static final String REQUEST_HANDLER_NATIVE = "/native";
|
||||
//public static final String REQUEST_HANDLER_ALFRESCO = "/alfresco";
|
||||
//public static final String REQUEST_HANDLER_SELECT = "/select";
|
||||
static final String REQUEST_HANDLER_GET = "/get";
|
||||
private static final String RESPONSE_DEFAULT_IDS = "response";
|
||||
static final String RESPONSE_DEFAULT_ID = "doc";
|
||||
|
||||
public static final String PREFIX_ERROR = "ERROR-";
|
||||
static final String PREFIX_ERROR = "ERROR-";
|
||||
|
||||
public static final String DOC_TYPE_NODE = "Node";
|
||||
public static final String DOC_TYPE_UNINDEXED_NODE = "UnindexedNode";
|
||||
public static final String DOC_TYPE_ERROR_NODE = "ErrorNode";
|
||||
private static final String DOC_TYPE_UNINDEXED_NODE = "UnindexedNode";
|
||||
private static final String DOC_TYPE_ERROR_NODE = "ErrorNode";
|
||||
public static final String DOC_TYPE_ACL = "Acl";
|
||||
public static final String DOC_TYPE_TX = "Tx";
|
||||
public static final String DOC_TYPE_ACL_TX = "AclTx";
|
||||
public static final String DOC_TYPE_STATE = "State";
|
||||
private static final String DOC_TYPE_STATE = "State";
|
||||
|
||||
public static final String SOLR_HOST = "solr.host";
|
||||
public static final String SOLR_PORT = "solr.port";
|
||||
@@ -235,7 +235,6 @@ public class SolrInformationServer implements InformationServer
|
||||
private final TrackerStats trackerStats = new TrackerStats(this);
|
||||
private final AlfrescoSolrDataModel dataModel;
|
||||
private final SolrContentStore solrContentStore;
|
||||
private final String alfrescoVersion;
|
||||
private final boolean transformContent;
|
||||
private final boolean recordUnindexedNodes;
|
||||
private final long lag;
|
||||
@@ -271,7 +270,7 @@ public class SolrInformationServer implements InformationServer
|
||||
|
||||
protected enum FTSStatus {New, Dirty, Clean}
|
||||
|
||||
class DocListCollector implements Collector, LeafCollector
|
||||
static class DocListCollector implements Collector, LeafCollector
|
||||
{
|
||||
private IntArrayList docs = new IntArrayList();
|
||||
private int docBase;
|
||||
@@ -303,7 +302,7 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
}
|
||||
|
||||
class TxnCacheFilter extends DelegatingCollector
|
||||
static class TxnCacheFilter extends DelegatingCollector
|
||||
{
|
||||
private NumericDocValues currentLongs;
|
||||
private Map txnLRU;
|
||||
@@ -330,7 +329,7 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
}
|
||||
|
||||
class TxnCollector extends DelegatingCollector
|
||||
static class TxnCollector extends DelegatingCollector
|
||||
{
|
||||
private NumericDocValues currentLongs;
|
||||
private long txnFloor;
|
||||
@@ -372,7 +371,7 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
}
|
||||
|
||||
class LRU extends LinkedHashMap<Long,Long>
|
||||
static class LRU extends LinkedHashMap<Long,Long>
|
||||
{
|
||||
private int maxSize;
|
||||
|
||||
@@ -395,7 +394,7 @@ public class SolrInformationServer implements InformationServer
|
||||
boolean isDefinitionExists(QName qName);
|
||||
}
|
||||
|
||||
abstract class TransactionInfoReporter
|
||||
static abstract class TransactionInfoReporter
|
||||
{
|
||||
protected final IndexHealthReport report;
|
||||
|
||||
@@ -428,7 +427,6 @@ public class SolrInformationServer implements InformationServer
|
||||
this.solrContentStore = solrContentStore;
|
||||
|
||||
Properties p = core.getResourceLoader().getCoreProperties();
|
||||
alfrescoVersion = p.getProperty("alfresco.version", "Unknown");
|
||||
transformContent = Boolean.parseBoolean(p.getProperty("alfresco.index.transformContent", "true"));
|
||||
recordUnindexedNodes = Boolean.parseBoolean(p.getProperty("alfresco.recordUnindexedNodes", "true"));
|
||||
lag = Integer.parseInt(p.getProperty("alfresco.lag", "1000"));
|
||||
@@ -505,11 +503,6 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
}
|
||||
|
||||
public String getAlfrescoVersion()
|
||||
{
|
||||
return this.alfrescoVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterInitModels()
|
||||
{
|
||||
@@ -1547,7 +1540,7 @@ public class SolrInformationServer implements InformationServer
|
||||
StringPropertyValue pValue = (StringPropertyValue) properties.get(ContentModel.PROP_IS_INDEXED);
|
||||
if (pValue != null)
|
||||
{
|
||||
boolean isIndexed = Boolean.valueOf(pValue.getValue());
|
||||
boolean isIndexed = Boolean.parseBoolean(pValue.getValue());
|
||||
if (!isIndexed)
|
||||
{
|
||||
LOGGER.debug("Clearing unindexed");
|
||||
@@ -1902,7 +1895,7 @@ public class SolrInformationServer implements InformationServer
|
||||
StringPropertyValue pValue = (StringPropertyValue) properties.get(ContentModel.PROP_IS_INDEXED);
|
||||
if (pValue != null)
|
||||
{
|
||||
boolean isIndexed = Boolean.valueOf(pValue.getValue());
|
||||
boolean isIndexed = Boolean.parseBoolean(pValue.getValue());
|
||||
if (!isIndexed)
|
||||
{
|
||||
LOGGER.debug("Clearing unindexed");
|
||||
@@ -1958,7 +1951,7 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
}
|
||||
|
||||
private void addToNewDocAndCache(NodeMetaData nodeMetaData, SolrInputDocument newDoc) throws IOException
|
||||
private void addToNewDocAndCache(NodeMetaData nodeMetaData, SolrInputDocument newDoc)
|
||||
{
|
||||
addFieldsToDoc(nodeMetaData, newDoc);
|
||||
SolrInputDocument cachedDoc = null;
|
||||
@@ -2232,7 +2225,7 @@ public class SolrInformationServer implements InformationServer
|
||||
.get(ContentModel.PROP_IS_CONTENT_INDEXED);
|
||||
if (pValue != null)
|
||||
{
|
||||
boolean isIndexed = Boolean.valueOf(pValue.getValue());
|
||||
boolean isIndexed = Boolean.parseBoolean(pValue.getValue());
|
||||
if (!isIndexed)
|
||||
{
|
||||
isContentIndexed = false;
|
||||
@@ -2374,13 +2367,19 @@ public class SolrInformationServer implements InformationServer
|
||||
|
||||
String transformationStatusFieldName = getSolrFieldNameForContentPropertyMetadata(propertyQName,
|
||||
AlfrescoSolrDataModel.ContentFieldType.TRANSFORMATION_STATUS);
|
||||
newDoc.addField(transformationStatusFieldName, cachedDoc.getFieldValue(transformationStatusFieldName));
|
||||
if (transformationStatusFieldName != null){
|
||||
newDoc.addField(transformationStatusFieldName, cachedDoc.getFieldValue(transformationStatusFieldName));
|
||||
}
|
||||
String transformationExceptionFieldName = getSolrFieldNameForContentPropertyMetadata(propertyQName,
|
||||
AlfrescoSolrDataModel.ContentFieldType.TRANSFORMATION_EXCEPTION);
|
||||
newDoc.addField(transformationExceptionFieldName, cachedDoc.getFieldValue(transformationExceptionFieldName));
|
||||
String transformationTimeFieldName = getSolrFieldNameForContentPropertyMetadata(propertyQName,
|
||||
if (transformationExceptionFieldName != null){
|
||||
newDoc.addField(transformationExceptionFieldName, cachedDoc.getFieldValue(transformationExceptionFieldName));
|
||||
}
|
||||
String transformationTimeFieldName = getSolrFieldNameForContentPropertyMetadata(propertyQName,
|
||||
AlfrescoSolrDataModel.ContentFieldType.TRANSFORMATION_TIME);
|
||||
newDoc.addField(transformationTimeFieldName, cachedDoc.getFieldValue(transformationTimeFieldName));
|
||||
if (transformationTimeFieldName != null){
|
||||
newDoc.addField(transformationTimeFieldName, cachedDoc.getFieldValue(transformationTimeFieldName));
|
||||
}
|
||||
|
||||
// Gets the new content docid and compares to that of the cachedDoc to mark the content as clean/dirty
|
||||
String fldName = getSolrFieldNameForContentPropertyMetadata(propertyQName,
|
||||
@@ -2393,7 +2392,7 @@ public class SolrInformationServer implements InformationServer
|
||||
|
||||
if(cachedDoc.getFieldValue(fldName) != null)
|
||||
{
|
||||
long cachedDocContentDocid = Long.valueOf(String.valueOf(cachedDoc.getFieldValue(fldName)));
|
||||
long cachedDocContentDocid = Long.parseLong(String.valueOf(cachedDoc.getFieldValue(fldName)));
|
||||
long currentContentDocid = contentPropertyValue.getId();
|
||||
// If we have used out of date content we mark it as dirty
|
||||
// Otherwise we leave it alone - it could already be marked as dirty/New and require an update
|
||||
@@ -3584,7 +3583,7 @@ public class SolrInformationServer implements InformationServer
|
||||
field, 1); // Min count of 1 ensures that the id returned is in the index
|
||||
for (Map.Entry<String, Integer> idCount : idCounts)
|
||||
{
|
||||
long idInIndex = Long.valueOf(idCount.getKey());
|
||||
long idInIndex = Long.parseLong(idCount.getKey());
|
||||
|
||||
// Only looks at facet values that fit the query
|
||||
if (batchStartId <= idInIndex && idInIndex <= batchEndId)
|
||||
@@ -3892,4 +3891,10 @@ public class SolrInformationServer implements InformationServer
|
||||
}
|
||||
return searchers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushContentStore() throws IOException
|
||||
{
|
||||
solrContentStore.flushChangeSet();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Behavioural interface which indicates the type of content store access mode for the owning node.
|
||||
* A replication interaction is composed at least by 2 roles: a master and a slave.
|
||||
*
|
||||
* Despite the same interface, the behaviour of the content store management changes depending on the node kind:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Master Node: READ + WRITE + Changes tracking. Writes and changes tracking are a direct consequence of the "indexing" nature of the master node.</li>
|
||||
* <li>Slave Node: READ ONLY (i.e. never write: changes are applied on the master and replicated on slaves)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Important: the owning entity *is not* the SolrCore instance: the Content store is shared across all cores of
|
||||
* A node can transit from Read to Write mode
|
||||
*
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
interface AccessMode extends Closeable
|
||||
{
|
||||
/**
|
||||
* Returns the last persisted content store version.
|
||||
*
|
||||
* @return the last persisted content store version, SolrContentStore#NO_VERSION_AVAILABLE in case the version isn't available.
|
||||
*/
|
||||
long getLastCommittedVersion();
|
||||
|
||||
/**
|
||||
* Persists the last committed version on the hosting node.
|
||||
* Note that this is tipically valid only on slave node, because the master already manages the content store version on
|
||||
* a persistent storage, so it doesn't need to call this method.
|
||||
*
|
||||
* @param version the last committed content store version.
|
||||
*/
|
||||
void setLastCommittedVersion(long version);
|
||||
|
||||
Map<String, List<Map<String, Object>>> getChanges(long version);
|
||||
|
||||
/**
|
||||
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
|
||||
*
|
||||
* @param tenant the owning tenant.
|
||||
* @param dbId the document DBID
|
||||
* @param doc the document itself.
|
||||
*/
|
||||
void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc);
|
||||
|
||||
/**
|
||||
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
|
||||
*
|
||||
* @param nodeMetaData the node metadata.
|
||||
* @param doc the document itself.
|
||||
*/
|
||||
void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc);
|
||||
|
||||
/**
|
||||
* Removes a node from the content store.
|
||||
*
|
||||
* @param nodeMetaData the node metadata.
|
||||
*/
|
||||
void removeDocFromContentStore(NodeMetaData nodeMetaData);
|
||||
|
||||
/**
|
||||
* Flushes pending changesets.
|
||||
*
|
||||
* @throws IOException in case of I/O failure.
|
||||
*/
|
||||
void flushChangeSet() throws IOException;
|
||||
|
||||
/**
|
||||
* Tries to transition from this mode to the readOnlyMode.
|
||||
*/
|
||||
void switchOnReadOnlyMode();
|
||||
|
||||
/**
|
||||
* Tries to transition from this mode to the read/write mode.
|
||||
*/
|
||||
void switchOnReadWriteMode();
|
||||
}
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import org.alfresco.util.Pair;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.LongPoint;
|
||||
import org.apache.lucene.document.NumericDocValuesField;
|
||||
import org.apache.lucene.document.StoredField;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.search.FieldDoc;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.SearcherManager;
|
||||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.search.SortedNumericSortField;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
/**
|
||||
* Stores and manages changes occurred in a content store.
|
||||
*
|
||||
* Note this entity is part of the content store only when it is set on writable mode (i.e. only when the hosting node
|
||||
* is a standalone instance or it is a master node).
|
||||
*
|
||||
* That means if the hosting node has at least one standalone core or one master node, then the managed content store
|
||||
* will be set in write mode and therefore a {@link ChangeSet} instance will track all changes applied to it.
|
||||
*
|
||||
* In that mode, each change is associated to a given (incremental) version; changes consist of adds/updates and deletes.
|
||||
* Each time the {@link org.alfresco.solr.tracker.CommitTracker} executes a commit in the main index, all content store
|
||||
* changes accumulated in the meantime in the current {@link ChangeSet} instance are flushed in an auxiliary Lucene index
|
||||
* and therefore persisted.
|
||||
*
|
||||
* On the other side, when all cores on a given node are configured as slaves, the content store is in read-only mode,
|
||||
* and there's no need to track any change (i.e. changes are coming from master through the replication procedure).
|
||||
*
|
||||
* As a side note: we can have a persistent or transient {@link ChangeSet} instance. The first one is used to manage, as
|
||||
* the name suggests, in a persistent way the content store changes accumulated during the indexing operations (see the
|
||||
* description above about its behaviour in master nodes).
|
||||
*
|
||||
* The second one is used instead for computing/reducing the "merged" list of changes that we need to communicate to slave nodes:
|
||||
* as part of the replication mechanism, a slave communicates its last synched content store version; on the master side,
|
||||
* we need to compute and communicate back all changes that the slave needs to apply since that version in order to be in
|
||||
* synch with master.
|
||||
*
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
public class ChangeSet implements AutoCloseable
|
||||
{
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(ChangeSet.class);
|
||||
private final static ChangeSet EMPTY_CHANGESET = new ChangeSet.Builder().empty().build();
|
||||
|
||||
/**
|
||||
* Builds class for creating {@link ChangeSet} instances.
|
||||
* The builder is here for creating three kind of {@link ChangeSet} instances:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Persistent: used for tracking and persisting content store changes.</li>
|
||||
* <li>Transient: used for computing the merged list of changes a slave should apply for being in synch with master.</li>
|
||||
* <li>Empty: an immutable "NullObject" used for denoting an empty {@link ChangeSet} instance.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Null_object_pattern">Null Object Design Pattern</a>
|
||||
*/
|
||||
public static class Builder
|
||||
{
|
||||
private String root;
|
||||
private boolean immutable;
|
||||
|
||||
/**
|
||||
* Adds a content store root folder to this builder.
|
||||
* This automatically implies we are creating a persistent {@link ChangeSet} instance.
|
||||
*
|
||||
* @param root the absolute path of the content store root folder.
|
||||
* @return this builder, for being used in a fluent mode.
|
||||
*/
|
||||
Builder withContentStoreRoot(String root)
|
||||
{
|
||||
if (root == null) throw new IllegalArgumentException("Unable to build the Changeset structures with a null content store root folder.");
|
||||
if (!new File(root).canWrite()) throw new IllegalArgumentException("Unable to build the Changeset structures with a non-writeable content store root folder.");
|
||||
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* We are interested in building an empty, immutable {@link ChangeSet} instance.
|
||||
*
|
||||
* @return this builder, for being used in a fluent mode.
|
||||
*/
|
||||
Builder empty()
|
||||
{
|
||||
this.root = null;
|
||||
this.immutable = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the product of this builder (i.e. the {@link ChangeSet} instance).
|
||||
*
|
||||
* @return the product of this builder (i.e. the {@link ChangeSet} instance).
|
||||
*/
|
||||
ChangeSet build()
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
// Creates a transient changeset (no persistence); mainly used for reducing the changes during replication.
|
||||
return new ChangeSet(
|
||||
immutable ? emptySet() : new HashSet<>(),
|
||||
immutable ? emptySet() : new HashSet<>());
|
||||
}
|
||||
|
||||
IndexWriter writer = null;
|
||||
try
|
||||
{
|
||||
File indexDirectory = new File(root, CHANGESETS_ROOT_FOLDER_NAME);
|
||||
|
||||
writer = new IndexWriter(FSDirectory.open(indexDirectory.toPath()), new IndexWriterConfig());
|
||||
writer.commit();
|
||||
|
||||
final SearcherManager searcher = new SearcherManager(writer, null);
|
||||
LOGGER.info("ContentStore Changeset index has been correctly mounted on {}", indexDirectory.getAbsolutePath());
|
||||
|
||||
return new ChangeSet(
|
||||
searcher,
|
||||
writer,
|
||||
immutable ? emptySet() : new HashSet<>(),
|
||||
immutable ? emptySet() : new HashSet<>());
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ofNullable(writer).ifPresent(ChangeSet::silentyClose);
|
||||
throw new IllegalArgumentException("Unable to create a ContentStore ChangeSet data structure. See further details in the stacktrack below.", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static String VERSION_FIELD_NAME = "version";
|
||||
private final static String RVERSION_FIELD_NAME = "rversion";
|
||||
private final static String ADDS_FIELD_NAME = "adds";
|
||||
private final static String DELETES_FIELD_NAME = "deletes";
|
||||
final static String CHANGESETS_ROOT_FOLDER_NAME = "changeSets";
|
||||
|
||||
Set<String> deletes;
|
||||
Set<String> adds;
|
||||
|
||||
SearcherManager searcher;
|
||||
private final IndexWriter writer;
|
||||
|
||||
Query selectEverything = new MatchAllDocsQuery();
|
||||
|
||||
/**
|
||||
* Builds a new transient {@link ChangeSet} with the given Lucene facades.
|
||||
*
|
||||
* @param deletesContainer the container which will hold the deletes.
|
||||
* @param addsContainer the container which will hold the adds/updates.
|
||||
*/
|
||||
private ChangeSet(
|
||||
final Set<String> deletesContainer,
|
||||
final Set<String> addsContainer)
|
||||
{
|
||||
this(null, null, deletesContainer, addsContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ChangeSet} with the given Lucene facades.
|
||||
*
|
||||
* @param searcher the searcher reference (actually a {@link SearcherManager} instance instead of dealing with {@link IndexSearcher} directly.
|
||||
* @param writer the {@link IndexWriter} instance used for persisting the content store changes.
|
||||
* @param deletesContainer the container which will hold the deletes.
|
||||
* @param addsContainer the container which will hold the adds/updates.
|
||||
*/
|
||||
private ChangeSet(
|
||||
final SearcherManager searcher,
|
||||
final IndexWriter writer,
|
||||
final Set<String> deletesContainer,
|
||||
final Set<String> addsContainer)
|
||||
{
|
||||
this.searcher = searcher;
|
||||
this.writer = writer;
|
||||
this.deletes = deletesContainer;
|
||||
this.adds = addsContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a delete change.
|
||||
*
|
||||
* @param path the relative path of the file which has been deleted.
|
||||
*/
|
||||
synchronized void delete(String path)
|
||||
{
|
||||
adds.remove(path);
|
||||
deletes.add(path);
|
||||
|
||||
LOGGER.debug("ContentStore change recorded: item {} has been deleted.", path);
|
||||
|
||||
debugPendingChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an add or update change.
|
||||
*
|
||||
* @param path the relative path of the file which has been updated or added.
|
||||
*/
|
||||
synchronized void addOrReplace(String path)
|
||||
{
|
||||
deletes.remove(path);
|
||||
adds.add(path);
|
||||
|
||||
LOGGER.debug("ContentStore change recorded: item {} has been added/updated.", path);
|
||||
|
||||
debugPendingChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all pending collected content store changes.
|
||||
*
|
||||
* @throws IOException in case of I/O failure.
|
||||
*/
|
||||
void flush() throws IOException
|
||||
{
|
||||
// No ops if this is a transient changeset
|
||||
if (searcher == null || writer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (adds.isEmpty() && deletes.isEmpty())
|
||||
{
|
||||
LOGGER.debug("No changes in contentstore to flush");
|
||||
return;
|
||||
}
|
||||
|
||||
final long version = System.currentTimeMillis();
|
||||
|
||||
LOGGER.debug("About to add a new Changeset entry (version = {}, deletes = {}, adds = {})", version, deletes.size(), adds.size());
|
||||
|
||||
final Document document = new Document();
|
||||
document.add(new NumericDocValuesField(VERSION_FIELD_NAME, version));
|
||||
document.add(new LongPoint(RVERSION_FIELD_NAME, version));
|
||||
|
||||
Set<String> tmpDel;
|
||||
Set<String> tmpAdd;
|
||||
|
||||
synchronized (this) {
|
||||
tmpDel = deletes;
|
||||
deletes = new HashSet<>();
|
||||
|
||||
tmpAdd = adds;
|
||||
adds = new HashSet<>();
|
||||
}
|
||||
|
||||
tmpDel.stream()
|
||||
.map(item -> new StoredField(DELETES_FIELD_NAME, item))
|
||||
.forEach(document::add);
|
||||
|
||||
tmpAdd.stream()
|
||||
.map(item -> new StoredField(ADDS_FIELD_NAME, item))
|
||||
.forEach(document::add);
|
||||
|
||||
writer.addDocument(document);
|
||||
writer.commit();
|
||||
searcher.maybeRefresh();
|
||||
|
||||
LOGGER.debug("New Changeset entry have been added (version = {}, deletes = {}, adds = {})", version, deletes.size(), adds.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity check for making sure the requestor sent a valid and known content store version.
|
||||
*
|
||||
* @param version the content store version (sent by a requestor)
|
||||
* @return true if the given version is unknown, that is, is not part of the content store version history.
|
||||
*/
|
||||
boolean isUnknownVersion(long version)
|
||||
{
|
||||
try
|
||||
{
|
||||
TopDocs hits = searcher().search(LongPoint.newExactQuery(RVERSION_FIELD_NAME, version), 1);
|
||||
return hits.totalHits != 1;
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
LOGGER.error("Unable to check the requested version ({}) in the local versioning store. See further details in the stacktrace below.", version, exception);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
ofNullable(writer).ifPresent(ChangeSet::silentyClose);
|
||||
ofNullable(searcher).ifPresent(ChangeSet::silentyClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last persisted content store version.
|
||||
*
|
||||
* @return the last persisted content store version, SolrContentStore#NO_VERSION_AVAILABLE in case the version isn't available.
|
||||
*/
|
||||
long getLastCommittedVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
TopDocs hits = searcher().search(
|
||||
selectEverything,
|
||||
1,
|
||||
new Sort(new SortedNumericSortField(VERSION_FIELD_NAME, SortField.Type.LONG, true)),
|
||||
false,
|
||||
false);
|
||||
|
||||
return ofNullable(hits.scoreDocs)
|
||||
.filter(docs -> docs.length > 0)
|
||||
.map(docs -> ((FieldDoc) docs[0]).fields)
|
||||
.filter(fields -> fields.length > 0)
|
||||
.map(fields -> (Long)fields[0])
|
||||
.orElse(SolrContentStore.NO_VERSION_AVAILABLE);
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
LOGGER.error("Unable to retrieve the last committed content store changeset version. " +
|
||||
"As consequence of that a dummy value of " + SolrContentStore.NO_VERSION_AVAILABLE +
|
||||
" will be returned. See further details in the stacktrack below.",
|
||||
exception);
|
||||
return SolrContentStore.NO_VERSION_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content store changes (adds / deletes) since the given version (exclusive).
|
||||
*
|
||||
* @param version the start offset version (exclusive).
|
||||
* @return the content store changes (adds / deletes) since the given version (exclusive).
|
||||
*/
|
||||
public ChangeSet since(long version)
|
||||
{
|
||||
try
|
||||
{
|
||||
Query query = LongPoint.newRangeQuery(RVERSION_FIELD_NAME, Math.addExact(version, 1), Long.MAX_VALUE);
|
||||
TopDocs hits = searcher().search(
|
||||
query,
|
||||
100,
|
||||
new Sort(new SortedNumericSortField(VERSION_FIELD_NAME, SortField.Type.LONG)),
|
||||
false,
|
||||
false);
|
||||
|
||||
final BiFunction<ChangeSet, ? super Pair<List<String>,List<String>>, ChangeSet> accumulator =
|
||||
(partial, nth) -> {
|
||||
final List<String> nthDeletes = nth.getFirst();
|
||||
final List<String> nthAdds = nth.getSecond();
|
||||
|
||||
nthDeletes.forEach(partial::delete);
|
||||
nthAdds.forEach(partial::addOrReplace);
|
||||
|
||||
return partial;
|
||||
};
|
||||
|
||||
final BinaryOperator<ChangeSet> combiner = (c1, c2) -> {
|
||||
c1.deletes.forEach(c2::delete);
|
||||
c1.adds.forEach(c2::addOrReplace);
|
||||
return c1;
|
||||
};
|
||||
|
||||
return stream(hits.scoreDocs)
|
||||
.map(this::toDoc)
|
||||
.map(doc ->
|
||||
new Pair<>(
|
||||
asList(doc.getValues(DELETES_FIELD_NAME)),
|
||||
asList(doc.getValues(ADDS_FIELD_NAME))))
|
||||
.reduce(new ChangeSet.Builder().build(), accumulator, combiner);
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
LOGGER.error("Unable to retrieve the changeset since version {}. " +
|
||||
"As consequence of that an empty result will be returned. " +
|
||||
"See further details in the stacktrack below.",
|
||||
version,
|
||||
exception);
|
||||
return EMPTY_CHANGESET;
|
||||
}
|
||||
}
|
||||
|
||||
private Document toDoc(ScoreDoc hit)
|
||||
{
|
||||
try
|
||||
{
|
||||
return searcher().doc(hit.doc);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void debugPendingChanges()
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("ContentStore pending deletes: " + deletes);
|
||||
LOGGER.debug("ContentStore pending adds/updates: " + adds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently close (i.e. without any exception re-throwing) the incoming resource.
|
||||
*
|
||||
* @param resource the closeable resource.
|
||||
*/
|
||||
private static void silentyClose(Closeable resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
resource.close();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LOGGER.error("Unable to properly close the resource instance {}. See further details in the stacktrace below.", resource, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private IndexSearcher searcher() throws IOException
|
||||
{
|
||||
return searcher.acquire();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
/**
|
||||
* {@link AccessMode} specialisation for those modes that need some kind of initialisation.
|
||||
*
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
public interface InitialisableAccessMode extends AccessMode
|
||||
{
|
||||
/**
|
||||
* Initialises this access mode instance.
|
||||
*/
|
||||
void init();
|
||||
}
|
||||
+588
-173
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -18,52 +18,489 @@
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.solr.AlfrescoSolrDataModel;
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.alfresco.solr.config.ConfigUtil;
|
||||
import org.alfresco.solr.handler.AlfrescoReplicationHandler;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.util.JavaBinCodec;
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.apache.solr.handler.SnapShooter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Optional.of;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.alfresco.solr.content.SolrContentUrlBuilder.FILE_EXTENSION;
|
||||
import static org.alfresco.solr.content.SolrContentUrlBuilder.logger;
|
||||
|
||||
/**
|
||||
* A content store specific to SOLR's requirements: The URL is generated from a
|
||||
* set of properties such as:
|
||||
*
|
||||
* <ul>
|
||||
* <li>ACL ID</li>
|
||||
* <li>DB ID</li>
|
||||
* <li>Other metadata</li>
|
||||
* <li>ACL ID</li>
|
||||
* <li>DB ID</li>
|
||||
* <li>Other metadata</li>
|
||||
* </ul>
|
||||
* The URL, if not known, can be reliably regenerated using the
|
||||
* {@link SolrContentUrlBuilder}.
|
||||
*
|
||||
*
|
||||
* The URL, if not known, can be reliably regenerated using the {@link SolrContentUrlBuilder}.
|
||||
* <br/>
|
||||
*
|
||||
* Since version 1.5 this class acts as a logical singleton: there will be only one instance per node.
|
||||
* That reflects exactly what we physically have in the filesystem (i.e. there's only one content store per node).
|
||||
*
|
||||
* That unique instance is created at startup in {@link org.alfresco.solr.AlfrescoCoreAdminHandler} and then passed to
|
||||
* each registered core (see {@link org.alfresco.solr.lifecycle.SolrCoreLoadListener})
|
||||
*
|
||||
* The State pattern implemented by means of the {@link AccessMode} interface allows for a given {@link SolrContentStore} instance to act:
|
||||
*
|
||||
* <ul>
|
||||
* <li>in READ/WRITE mode: when <b>at least one core</b> of the hosting node is a master or it is a standalone shard/instance.</li>
|
||||
* <li>in READ ONLY mode: when <b>all cores</b> of the hosting node are slaves.</li>
|
||||
* </ul>
|
||||
*
|
||||
* The Finite State Machine (FST) provides three possible states: Initial, Read Only, Read/Write.
|
||||
* The allowed transitions are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Initial -> ReadOnly: a slave core has been registered, the content store hasn't been yet initialised.</li>
|
||||
* <li>
|
||||
* ReadOnly -> Read/Write: this scenario is unusual because we should have on the same instance a mixed set of cores
|
||||
* (some master/standalone, some slaves). It happens when a first slave core registers and causes the transition
|
||||
* described in the first point (initial -> read only). Then a master or standalone core registers so we need to
|
||||
* move from a read only to a complete readable/writable managed content store.
|
||||
* </li>
|
||||
* <li> Initial -> Read/Write: a master or standalone core has been registered, and the content store hasn't been yet initialised.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Note the following transitions are not allowed:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Coming back to Initial state: once it has been initialised the Content Store cannot return back to the "Initial" state.</li>
|
||||
* <li>
|
||||
* Read/Write -> ReadOnly: if at least one master or standalone core registers, the content store is permanently moved in Read/Write mode.
|
||||
* So even a further slave node registers (not very usually as described above) the content store remains in RW mode.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @author Michael Suzuki
|
||||
* @since 5.0
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
* @see org.alfresco.solr.lifecycle.SolrCoreLoadListener
|
||||
* @see <a href="https://it.wikipedia.org/wiki/State_pattern">State Pattern</a>
|
||||
*/
|
||||
public class SolrContentStore implements ContentStore
|
||||
public final class SolrContentStore implements Closeable, AccessMode
|
||||
{
|
||||
protected final static Logger log = LoggerFactory.getLogger(SolrContentStore.class);
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(SolrContentStore.class);
|
||||
|
||||
public static final long NO_VERSION_AVAILABLE = -1L;
|
||||
public static final long NO_CONTENT_STORE_REPLICATION_REQUIRED = -2L;
|
||||
|
||||
static final String CONTENT_STORE = "contentstore";
|
||||
static final String SOLR_CONTENT_DIR = "solr.content.dir";
|
||||
|
||||
public static final String INFO = "info";
|
||||
public static final String FULL_REPLICATION = "full-replication";
|
||||
public static final String DELETES = "deletes";
|
||||
public static final String ADDS = "adds";
|
||||
|
||||
private final Predicate<File> onlyDatafiles = file -> file.isFile() && file.getName().endsWith(FILE_EXTENSION);
|
||||
private final String root;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param solrHome
|
||||
* Used for denoting the very beginning state, when the {@link SolrContentStore} has been created
|
||||
* but we don't know (yet) which is the role played by the cores that will register on the hosting Solr node.
|
||||
*/
|
||||
AccessMode notYetSet = new AccessMode()
|
||||
{
|
||||
@Override
|
||||
public long getLastCommittedVersion()
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastCommittedVersion(long version)
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<Map<String, Object>>> getChanges(long version)
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChangeSet()
|
||||
{
|
||||
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadOnlyMode()
|
||||
{
|
||||
logger.info("Switching the content store to ReadOnly mode.");
|
||||
currentAccessMode = readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadWriteMode()
|
||||
{
|
||||
logger.info("Switching the content store to Read/Write mode.");
|
||||
readWrite.init();
|
||||
currentAccessMode = readWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
// Nothing to close here
|
||||
}
|
||||
};
|
||||
|
||||
final InitialisableAccessMode readOnly = new InitialisableAccessMode()
|
||||
{
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
// Nothing to be done here...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadWriteMode()
|
||||
{
|
||||
logger.info("Switching from ReadOnly to Read/Write Content Store.");
|
||||
|
||||
readWrite.init();
|
||||
currentAccessMode = readWrite;
|
||||
|
||||
logger.info("Switching from ReadOnly to Read/Write Content Store.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadOnlyMode()
|
||||
{
|
||||
logger.info("The content store is already in ReadOnly mode so this call won't have any effect.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastCommittedVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.readAllLines(Paths.get(root, ".version"))
|
||||
.stream()
|
||||
.map(Long::parseLong)
|
||||
.findFirst()
|
||||
.orElse(NO_VERSION_AVAILABLE);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NO_VERSION_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastCommittedVersion(long version)
|
||||
{
|
||||
|
||||
File tmpFile = new File(root, ".version-" + new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(new Date()));
|
||||
try
|
||||
{
|
||||
FileWriter wr = new FileWriter(tmpFile);
|
||||
wr.write(Long.toString(version));
|
||||
wr.close();
|
||||
|
||||
// file.renameTo(..) does not work on windows. Use Files.move instead.
|
||||
Files.move(tmpFile.toPath(), new File(root, ".version").toPath(), StandardCopyOption.ATOMIC_MOVE);
|
||||
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
logger.error("Unable to persist the last committed content store version {}. See the stacktrace below for furtger details.", version, exception);
|
||||
try
|
||||
{
|
||||
Files.delete(tmpFile.toPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Unable to delete tmp contentstore version file {}.", version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<Map<String, Object>>> getChanges(long version)
|
||||
{
|
||||
logger.warn("NoOp SolrContentStore changes call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
|
||||
{
|
||||
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
|
||||
{
|
||||
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
|
||||
{
|
||||
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChangeSet()
|
||||
{
|
||||
logger.warn("NoOp ChangeSet tracking call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
// There's nothing to close on slave side
|
||||
}
|
||||
};
|
||||
|
||||
final InitialisableAccessMode readWrite = new InitialisableAccessMode()
|
||||
{
|
||||
private ChangeSet changeSet;
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
changeSet = ofNullable(changeSet).orElseGet(() -> new ChangeSet.Builder().withContentStoreRoot(root).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastCommittedVersion()
|
||||
{
|
||||
return changeSet.getLastCommittedVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastCommittedVersion(long version)
|
||||
{
|
||||
// Do nothing here, as we are on master-side
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<Map<String, Object>>> getChanges(long version)
|
||||
{
|
||||
// The slave doesn't have a version, we are listing the whole content store
|
||||
if (version <= NO_VERSION_AVAILABLE || changeSet.isUnknownVersion(version))
|
||||
{
|
||||
String message = "A slave requested the content store synchronization " +
|
||||
((version <= NO_VERSION_AVAILABLE)
|
||||
? " without providing any local version (actually {})."
|
||||
: " with an invalid/unknown local version number ({}).") +
|
||||
"As consequence of that this master will list the whole content store because a full replication is needed.";
|
||||
|
||||
logger.info(message, version);
|
||||
|
||||
return Map.of(
|
||||
INFO, singletonList(Map.of(FULL_REPLICATION, true)),
|
||||
ADDS, fullContentStore(),
|
||||
DELETES, emptyList());
|
||||
}
|
||||
|
||||
ChangeSet changes = changeSet.since(version);
|
||||
|
||||
return Map.of(
|
||||
INFO, singletonList(Map.of(FULL_REPLICATION, false)),
|
||||
DELETES,
|
||||
changes.deletes.stream()
|
||||
.map(path -> Map.<String, Object>of("name", path))
|
||||
.collect(toList()),
|
||||
ADDS,
|
||||
changes.adds.stream()
|
||||
.map(relativePath -> root + relativePath)
|
||||
.map(File::new)
|
||||
.map(file -> new AlfrescoReplicationHandler.FileInfo(file, file.getAbsolutePath().replace(root, "")))
|
||||
.map(AlfrescoReplicationHandler.FileInfo::getAsMap)
|
||||
.collect(toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
|
||||
{
|
||||
ContentContext contentContext =
|
||||
of(SolrContentUrlBuilder
|
||||
.start()
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)))
|
||||
.map(SolrContentUrlBuilder::getContentContext)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unable to build a Content Context from tenant " + tenant + " and DBID " + dbId));
|
||||
|
||||
this.delete(contentContext.getContentUrl());
|
||||
|
||||
ContentWriter writer = this.getWriter(contentContext);
|
||||
|
||||
LOGGER.debug("Writing {}/{} to {}", tenant, dbId, contentContext.getContentUrl());
|
||||
|
||||
try (OutputStream contentOutputStream = writer.getContentOutputStream();
|
||||
GZIPOutputStream gzip = new GZIPOutputStream(contentOutputStream))
|
||||
{
|
||||
JavaBinCodec codec = new JavaBinCodec(resolver);
|
||||
codec.marshal(doc, gzip);
|
||||
|
||||
File file = getFileFromUrl(contentContext.getContentUrl());
|
||||
changeSet.addOrReplace(relativePath(file));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LOGGER.warn("Unable to write to Content Store using URL: {}", contentContext.getContentUrl(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc) {
|
||||
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
|
||||
storeDocOnSolrContentStore(fixedTenantDomain, nodeMetaData.getId(), doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
|
||||
{
|
||||
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
|
||||
String contentUrl = SolrContentUrlBuilder
|
||||
.start()
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, fixedTenantDomain)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(nodeMetaData.getId()))
|
||||
.getContentContext()
|
||||
.getContentUrl();
|
||||
delete(contentUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChangeSet() throws IOException
|
||||
{
|
||||
changeSet.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadWriteMode()
|
||||
{
|
||||
logger.debug("The content store is already in ReadWrite mode; as consequence of that, the incoming \"SET-TO-RW-MODE\" call won't have any effect.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadOnlyMode()
|
||||
{
|
||||
logger.debug("A writable content store cannot switch in ReadOnly mode. This could happen in an edge case where" +
|
||||
" on the same Solr node we have masters and slaves nodes");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
changeSet.close();
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> fullContentStore()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.walk(Paths.get(root))
|
||||
.map(Path::toFile)
|
||||
.filter(onlyDatafiles)
|
||||
.map(file -> new AlfrescoReplicationHandler.FileInfo(file, file.getAbsolutePath().replace(root, "")))
|
||||
.map(AlfrescoReplicationHandler.FileInfo::getAsMap)
|
||||
.collect(toList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("An exception occurred while retrieving the whole ContentStore filelist. " +
|
||||
"As consequence of that an empty list will be returned (i.e. no ContentStore synch will happen).");
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private void delete(String contentUrl)
|
||||
{
|
||||
File file = getFileFromUrl(contentUrl);
|
||||
if (file.delete()) changeSet.delete(relativePath(file));
|
||||
}
|
||||
|
||||
private ContentWriter getWriter(ContentContext context)
|
||||
{
|
||||
String url = context.getContentUrl();
|
||||
File file = getFileFromUrl(url);
|
||||
return new SolrFileContentWriter(file, url);
|
||||
}
|
||||
};
|
||||
|
||||
AccessMode currentAccessMode = notYetSet;
|
||||
|
||||
private final JavaBinCodec.ObjectResolver resolver = (o, codec) -> {
|
||||
if(o instanceof BytesRef)
|
||||
{
|
||||
BytesRef br = (BytesRef)o;
|
||||
codec.writeByteArray(br.bytes,br.offset,br.length);
|
||||
return null;
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a new {@link SolrContentStore} instance with the given SOLR HOME.
|
||||
*
|
||||
* @param solrHome the Solr HOME.
|
||||
*/
|
||||
public SolrContentStore(String solrHome)
|
||||
{
|
||||
@@ -77,122 +514,92 @@ public class SolrContentStore implements ContentStore
|
||||
{
|
||||
//Its very unlikely that solrHome would not exist so we will log an error
|
||||
//but continue because solr.content.dir may be specified, so it keeps working
|
||||
log.error(solrHomeFile.getAbsolutePath() + " does not exist.");
|
||||
LOGGER.error(solrHomeFile.getAbsolutePath() + " does not exist.");
|
||||
}
|
||||
|
||||
String path = solrHomeFile.getParent()+"/"+CONTENT_STORE;
|
||||
log.warn(path + " will be used as a default path if " + SOLR_CONTENT_DIR + " property is not defined");
|
||||
|
||||
String path = solrHomeFile.getParent() + "/" + CONTENT_STORE;
|
||||
LOGGER.warn(path + " will be used as a default path if " + SOLR_CONTENT_DIR + " property is not defined");
|
||||
File rootFile = new File(ConfigUtil.locateProperty(SOLR_CONTENT_DIR, path));
|
||||
|
||||
try
|
||||
{
|
||||
FileUtils.forceMkdir(rootFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create directory for content store: " + rootFile, e);
|
||||
}
|
||||
|
||||
this.root = rootFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
// write a BytesRef as a byte array
|
||||
private JavaBinCodec.ObjectResolver resolver = new JavaBinCodec.ObjectResolver()
|
||||
/**
|
||||
* Returns the content store changes since the given (requestor) version.
|
||||
*
|
||||
* @param version the requestor version.
|
||||
* @return the content store changes since the given (requestor) version.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<Map<String, Object>>> getChanges(long version)
|
||||
{
|
||||
@Override public Object resolve(Object o,JavaBinCodec codec)throws IOException
|
||||
{
|
||||
if(o instanceof BytesRef)
|
||||
{
|
||||
BytesRef br=(BytesRef)o;
|
||||
codec.writeByteArray(br.bytes,br.offset,br.length);
|
||||
return null;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
};
|
||||
return currentAccessMode.getChanges(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve document from SolrContentStore.
|
||||
*
|
||||
* @param tenant identifier
|
||||
* @param dbId identifier
|
||||
* @return {@link SolrInputDocument} searched document
|
||||
* @throws IOException if error
|
||||
*/
|
||||
public SolrInputDocument retrieveDocFromSolrContentStore(String tenant, long dbId) throws IOException
|
||||
public SolrInputDocument retrieveDocFromSolrContentStore(String tenant, long dbId)
|
||||
{
|
||||
String contentUrl = SolrContentUrlBuilder.start().add(SolrContentUrlBuilder.KEY_TENANT, tenant)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)).get();
|
||||
String contentUrl =
|
||||
SolrContentUrlBuilder.start()
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
|
||||
.get();
|
||||
|
||||
ContentReader reader = this.getReader(contentUrl);
|
||||
SolrInputDocument cachedDoc = null;
|
||||
if (reader.exists())
|
||||
if (!reader.exists())
|
||||
{
|
||||
// try-with-resources statement closes all these InputStreams
|
||||
try (InputStream contentInputStream = reader.getContentInputStream();
|
||||
// Uncompresses the document
|
||||
GZIPInputStream gzip = new GZIPInputStream(contentInputStream);)
|
||||
{
|
||||
cachedDoc = (SolrInputDocument) new JavaBinCodec(resolver).unmarshal(gzip);
|
||||
} catch (Exception e)
|
||||
{
|
||||
// Don't fail for this
|
||||
log.warn("Failed to get doc from store using URL: " + contentUrl, e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream contentInputStream = reader.getContentInputStream();
|
||||
InputStream gzip = new GZIPInputStream(contentInputStream))
|
||||
{
|
||||
return (SolrInputDocument) new JavaBinCodec(resolver).unmarshal(gzip);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// Don't fail for this
|
||||
LOGGER.warn("Failed to get doc from store using URL: " + contentUrl, exception);
|
||||
return null;
|
||||
}
|
||||
return cachedDoc;
|
||||
}
|
||||
|
||||
private final String root;
|
||||
|
||||
@Override
|
||||
public boolean isContentUrlSupported(String contentUrl)
|
||||
public long getLastCommittedVersion()
|
||||
{
|
||||
return (contentUrl != null && contentUrl.startsWith(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX));
|
||||
return currentAccessMode.getLastCommittedVersion();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <tt>true</tt> always
|
||||
*/
|
||||
@Override
|
||||
public boolean isWriteSupported()
|
||||
public void setLastCommittedVersion(long version)
|
||||
{
|
||||
return true;
|
||||
currentAccessMode.setLastCommittedVersion(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1 always
|
||||
* Returns the absolute path of the content store root folder.
|
||||
*
|
||||
* @return the absolute path of the content store root folder.
|
||||
*/
|
||||
@Override
|
||||
public long getSpaceFree()
|
||||
{
|
||||
return -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1 always
|
||||
*/
|
||||
@Override
|
||||
public long getSpaceTotal()
|
||||
{
|
||||
return -1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootLocation()
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a content URL into a File, whether it exists or not
|
||||
*/
|
||||
private File getFileFromUrl(String contentUrl)
|
||||
{
|
||||
String path = contentUrl.replace(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX, root + "/");
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String contentUrl)
|
||||
{
|
||||
File file = getFileFromUrl(contentUrl);
|
||||
@@ -200,93 +607,101 @@ public class SolrContentStore implements ContentStore
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentReader getReader(String contentUrl)
|
||||
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
|
||||
{
|
||||
currentAccessMode.storeDocOnSolrContentStore(tenant, dbId, doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store {@link SolrInputDocument} in to Alfresco solr content store.
|
||||
*
|
||||
* @param nodeMetaData the incoming node metadata.
|
||||
* @param doc the document itself.
|
||||
*/
|
||||
@Override
|
||||
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
|
||||
{
|
||||
currentAccessMode.storeDocOnSolrContentStore(nodeMetaData, doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes {@link SolrInputDocument} from Alfresco solr content store.
|
||||
*
|
||||
* @param nodeMetaData the incoming node metadata.
|
||||
*/
|
||||
@Override
|
||||
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
|
||||
{
|
||||
currentAccessMode.removeDocFromContentStore(nodeMetaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChangeSet() throws IOException
|
||||
{
|
||||
currentAccessMode.flushChangeSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
currentAccessMode.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadWriteMode()
|
||||
{
|
||||
currentAccessMode = readWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchOnReadOnlyMode()
|
||||
{
|
||||
currentAccessMode = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming the input file belongs to the content store, it returns the corresponding relative path.
|
||||
*
|
||||
* @param file the content store file.
|
||||
* @return the relative file path.
|
||||
*/
|
||||
private String relativePath(File file)
|
||||
{
|
||||
return file.getAbsolutePath().replace(root, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a content URL into a File, whether it exists or not
|
||||
*/
|
||||
private File getFileFromUrl(String contentUrl)
|
||||
{
|
||||
return new File(contentUrl.replace(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX, root + "/"));
|
||||
}
|
||||
|
||||
private ContentReader getReader(String contentUrl)
|
||||
{
|
||||
File file = getFileFromUrl(contentUrl);
|
||||
return new SolrFileContentReader(file, contentUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentWriter getWriter(ContentContext context)
|
||||
{
|
||||
// Ensure that there is a context and that it has a URL
|
||||
if (context == null || context.getContentUrl() == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Retrieve a writer with a URL-providing ContentContext.");
|
||||
}
|
||||
String url = context.getContentUrl();
|
||||
File file = getFileFromUrl(url);
|
||||
SolrFileContentWriter writer = new SolrFileContentWriter(file, url);
|
||||
// Done
|
||||
return writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(String contentUrl)
|
||||
{
|
||||
File file = getFileFromUrl(contentUrl);
|
||||
return file.delete();
|
||||
}
|
||||
/**
|
||||
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
|
||||
* @param tenant
|
||||
* @param dbId
|
||||
* @param doc
|
||||
* @throws IOException
|
||||
* Enables/disables the content store access mode.
|
||||
* The term "toggles" is just for indicating that this method will be called several times (one for each registered
|
||||
* core). The underlying FSM makes sure this call will be idempotent so in case of read/write content store the
|
||||
* data structure used for maintaining the content store versioning will be initialised only once, even if this
|
||||
* method is called repeatedly.
|
||||
*
|
||||
* @param enableReadOnlyMode a flag indicating if the requesting core requires a readOnly (true) or readWrite (false) content store.
|
||||
*/
|
||||
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc) throws IOException
|
||||
public synchronized void toggleReadOnlyMode(boolean enableReadOnlyMode)
|
||||
{
|
||||
ContentContext contentContext = SolrContentUrlBuilder
|
||||
.start()
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
|
||||
.getContentContext();
|
||||
this.delete(contentContext.getContentUrl());
|
||||
ContentWriter writer = this.getWriter(contentContext);
|
||||
if (log.isDebugEnabled())
|
||||
if (enableReadOnlyMode)
|
||||
{
|
||||
log.debug("Writing doc to " + contentContext.getContentUrl());
|
||||
currentAccessMode.switchOnReadOnlyMode();
|
||||
}
|
||||
try (
|
||||
OutputStream contentOutputStream = writer.getContentOutputStream();
|
||||
// Compresses the document
|
||||
GZIPOutputStream gzip = new GZIPOutputStream(contentOutputStream);
|
||||
)
|
||||
else
|
||||
{
|
||||
JavaBinCodec codec = new JavaBinCodec(resolver);
|
||||
codec.marshal(doc, gzip);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// A failure to write to the store is acceptable as long as it's logged
|
||||
log.warn("Failed to write to store using URL: " + contentContext.getContentUrl(), e);
|
||||
currentAccessMode.switchOnReadWriteMode();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Store {@link SolrInputDocument} in to Alfresco solr content store.
|
||||
* @param nodeMetaData identifier
|
||||
* @param doc to store
|
||||
* @throws IOException if error
|
||||
*/
|
||||
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc) throws IOException
|
||||
{
|
||||
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
|
||||
storeDocOnSolrContentStore(fixedTenantDomain, nodeMetaData.getId(), doc);
|
||||
}
|
||||
/**
|
||||
* Removes {@link SolrInputDocument} from Alfresco solr content store.
|
||||
* @param nodeMetaData
|
||||
*/
|
||||
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
|
||||
{
|
||||
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
|
||||
String contentUrl = SolrContentUrlBuilder
|
||||
.start()
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, fixedTenantDomain)
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(nodeMetaData.getId()))
|
||||
.getContentContext()
|
||||
.getContentUrl();
|
||||
this.delete(contentUrl);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+28
-41
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.zip.CRC32;
|
||||
@@ -43,20 +43,13 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class SolrContentUrlBuilder
|
||||
{
|
||||
/**
|
||||
* <b>solr</b> is the prefix for SOLR content URLs
|
||||
* @see #isContentUrlSupported(String)
|
||||
*/
|
||||
public static final String SOLR_PROTOCOL = "solr";
|
||||
public static final String SOLR_PROTOCOL_PREFIX = SOLR_PROTOCOL + ContentStore.PROTOCOL_DELIMITER;
|
||||
public static final String FILE_EXTENSION = ".gz";
|
||||
private static final String SOLR_PROTOCOL = "solr";
|
||||
static final String SOLR_PROTOCOL_PREFIX = SOLR_PROTOCOL + ContentStore.PROTOCOL_DELIMITER;
|
||||
static final String FILE_EXTENSION = ".gz";
|
||||
|
||||
/** The key for the tenant name */
|
||||
public static final String KEY_TENANT = "tenant";
|
||||
/** The key for the DB ID */
|
||||
public static final String KEY_DB_ID = "dbId";
|
||||
/** The key for the ACL ID */
|
||||
public static final String KEY_ACL_ID = "aclId";
|
||||
static final String KEY_TENANT = "tenant";
|
||||
static final String KEY_DB_ID = "dbId";
|
||||
static final String KEY_ACL_ID = "aclId";
|
||||
|
||||
protected final static Logger logger = LoggerFactory.getLogger(SolrContentUrlBuilder.class);
|
||||
|
||||
@@ -66,9 +59,9 @@ public class SolrContentUrlBuilder
|
||||
/**
|
||||
* Protected constructor used by {@link SolrContentUrlBuilder#start()}
|
||||
*/
|
||||
protected SolrContentUrlBuilder()
|
||||
private SolrContentUrlBuilder()
|
||||
{
|
||||
this.metadata = new TreeMap<String, String>();
|
||||
this.metadata = new TreeMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,32 +76,34 @@ public class SolrContentUrlBuilder
|
||||
|
||||
/**
|
||||
* Add some metadata to the URL generator. The order in which metadata is added is irrelevant.
|
||||
* <p/>
|
||||
* Note that there are specific keys that are commonly used and, if provided, may not be null or empty.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>{@link #KEY_TENANT}:</b> The name of the tenant or 'default' if missing.</li>
|
||||
* <li><b>{@link #KEY_DB_ID}:</b> The database ID.</li>
|
||||
* <li><b>{@link #KEY_ACL_ID}:</b> The ACL ID.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param key an arbitrary metadata key (never <tt>null</tt>>
|
||||
* @param value some metadata value (<tt>null</tt> is supported)
|
||||
* @return this builder for more building
|
||||
* @param key an arbitrary metadata key (never <tt>null</tt>>
|
||||
* @param value some metadata value (<tt>null</tt> is supported)
|
||||
* @return this builder for more building
|
||||
*
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
* @throws IllegalStateException if the key has been used already
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
* @throws IllegalStateException if the key has been used already
|
||||
*/
|
||||
public synchronized SolrContentUrlBuilder add(String key, String value)
|
||||
public SolrContentUrlBuilder add(String key, String value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new IllegalArgumentException("The metadata 'key' may not be null.");
|
||||
}
|
||||
|
||||
String previous = metadata.put(key, value);
|
||||
if (previous != null)
|
||||
{
|
||||
throw new IllegalStateException("The metadata key, '" + key + "', has already been used.");
|
||||
}
|
||||
|
||||
// Check well-known keys
|
||||
if (key.equals(KEY_TENANT) || key.equals(KEY_DB_ID) || key.equals(KEY_ACL_ID))
|
||||
{
|
||||
@@ -131,8 +126,8 @@ public class SolrContentUrlBuilder
|
||||
/**
|
||||
* Get the final content URL using the {@link #add(String, String) supplied metadata}.
|
||||
*
|
||||
* @return the SOLR content URL
|
||||
* @throws IllegalStateException if no metadata has been added
|
||||
* @return the SOLR content URL
|
||||
* @throws IllegalStateException if no metadata has been added
|
||||
*/
|
||||
public synchronized String get()
|
||||
{
|
||||
@@ -169,21 +164,13 @@ public class SolrContentUrlBuilder
|
||||
sb.append("misc/");
|
||||
// Calculate the CRC
|
||||
CRC32 crc = new CRC32();
|
||||
try
|
||||
for (Map.Entry<String, String> entry : metadata.entrySet())
|
||||
{
|
||||
for (Map.Entry<String, String> entry : metadata.entrySet())
|
||||
{
|
||||
// This is ordered, so just add each entry as "key = value".
|
||||
// DO NOT USE entry.toString() because the format is not a contract
|
||||
// and we have to have the same string for the same metadata
|
||||
String entryStr = entry.getKey() + "=" + entry.getValue() + "; ";
|
||||
crc.update(entryStr.getBytes("UTF-8"));
|
||||
}
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
// Yeah, right.
|
||||
throw new RuntimeException("UTF-8 is not supported.", e);
|
||||
// This is ordered, so just add each entry as "key = value".
|
||||
// DO NOT USE entry.toString() because the format is not a contract
|
||||
// and we have to have the same string for the same metadata
|
||||
String entryStr = entry.getKey() + "=" + entry.getValue() + "; ";
|
||||
crc.update(entryStr.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
numSb.append(crc.getValue());
|
||||
}
|
||||
@@ -218,7 +205,7 @@ public class SolrContentUrlBuilder
|
||||
/**
|
||||
* Helper method to retrieve a {@link ContentContext} constructed using the final {@link #get()} url.
|
||||
*/
|
||||
public ContentContext getContentContext()
|
||||
ContentContext getContentContext()
|
||||
{
|
||||
String url = get();
|
||||
return new ContentContext(null, url);
|
||||
|
||||
+7
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -42,7 +42,7 @@ import org.springframework.util.FileCopyUtils;
|
||||
* @author Derek Hulley
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SolrFileContentReader implements ContentReader
|
||||
class SolrFileContentReader implements ContentReader
|
||||
{
|
||||
private final File file;
|
||||
private final String contentUrl;
|
||||
@@ -51,7 +51,7 @@ public class SolrFileContentReader implements ContentReader
|
||||
* @param file the file to write to
|
||||
* @param contentUrl the content URL for information purposes
|
||||
*/
|
||||
protected SolrFileContentReader(File file, String contentUrl)
|
||||
SolrFileContentReader(File file, String contentUrl)
|
||||
{
|
||||
this.file = file;
|
||||
this.contentUrl = contentUrl;
|
||||
@@ -120,9 +120,8 @@ public class SolrFileContentReader implements ContentReader
|
||||
}
|
||||
try
|
||||
{
|
||||
InputStream is = new BufferedInputStream(new FileInputStream(file));
|
||||
// done
|
||||
return is;
|
||||
return new BufferedInputStream(new FileInputStream(file));
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
@@ -192,12 +191,9 @@ public class SolrFileContentReader implements ContentReader
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
FileCopyUtils.copy(is, os); // both streams are closed
|
||||
byte[] bytes = os.toByteArray();
|
||||
// get the encoding for the string
|
||||
|
||||
String encoding = "UTF-8";
|
||||
// create the string from the byte[] using encoding if necessary
|
||||
String content = (encoding == null) ? new String(bytes) : new String(bytes, encoding);
|
||||
// done
|
||||
return content;
|
||||
return new String(bytes, encoding);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
@@ -266,4 +262,4 @@ public class SolrFileContentReader implements ContentReader
|
||||
{
|
||||
throw new UnsupportedOperationException("Auto-created method not implemented.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -41,7 +41,7 @@ import org.apache.commons.io.FileUtils;
|
||||
* @author Derek Hulley
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SolrFileContentWriter implements ContentWriter
|
||||
class SolrFileContentWriter implements ContentWriter
|
||||
{
|
||||
private final File file;
|
||||
private final String contentUrl;
|
||||
@@ -51,11 +51,10 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
* @param file the file to write to
|
||||
* @param contentUrl the content URL for information purposes
|
||||
*/
|
||||
protected SolrFileContentWriter(File file, String contentUrl)
|
||||
SolrFileContentWriter(File file, String contentUrl)
|
||||
{
|
||||
this.file = file;
|
||||
this.contentUrl = contentUrl;
|
||||
this.written = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,7 +108,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
@Override
|
||||
public synchronized OutputStream getContentOutputStream() throws ContentIOException
|
||||
{
|
||||
if (written == true)
|
||||
if (written)
|
||||
{
|
||||
throw new IllegalStateException("The writer has already been used: " + file);
|
||||
}
|
||||
@@ -117,6 +116,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
{
|
||||
throw new IllegalStateException("The file already exists: " + file);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
OutputStream is = new BufferedOutputStream(FileUtils.openOutputStream(file));
|
||||
@@ -139,7 +139,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
@Override
|
||||
public synchronized void putContent(InputStream is) throws ContentIOException
|
||||
{
|
||||
if (written == true)
|
||||
if (written)
|
||||
{
|
||||
throw new IllegalStateException("The writer has already been used: " + file);
|
||||
}
|
||||
@@ -147,6 +147,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
{
|
||||
throw new IllegalStateException("The file already exists: " + file);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileUtils.copyInputStreamToFile(is, file);
|
||||
@@ -161,7 +162,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
@Override
|
||||
public synchronized void putContent(File sourceFile) throws ContentIOException
|
||||
{
|
||||
if (written == true)
|
||||
if (written)
|
||||
{
|
||||
throw new IllegalStateException("The writer has already been used: " + this.file);
|
||||
}
|
||||
@@ -173,6 +174,7 @@ public class SolrFileContentWriter implements ContentWriter
|
||||
{
|
||||
throw new IllegalStateException("The source file does not exist: " + sourceFile);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileUtils.copyFile(sourceFile, this.file, false);
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* The package contains all components that manage the Solr ContentStore.
|
||||
* Note that in SearchServices 2.0 the whole package will be deprecated/removed because the content store will be
|
||||
* replaced by the built-in Solr storage capability (i.e. stored fields).
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
+2762
File diff suppressed because it is too large
Load Diff
+2377
File diff suppressed because it is too large
Load Diff
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* Modification copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
package org.alfresco.solr.handler;
|
||||
|
||||
import org.apache.solr.handler.SnapShooter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
class OldBackupDirectory implements Comparable<OldBackupDirectory>
|
||||
{
|
||||
private static final Pattern dirNamePattern = Pattern.compile("^snapshot[.](.*)$");
|
||||
|
||||
private URI basePath;
|
||||
private String dirName;
|
||||
private Optional<Date> timestamp = Optional.empty();
|
||||
|
||||
OldBackupDirectory(URI basePath, String dirName)
|
||||
{
|
||||
this.dirName = Objects.requireNonNull(dirName);
|
||||
this.basePath = Objects.requireNonNull(basePath);
|
||||
Matcher m = dirNamePattern.matcher(dirName);
|
||||
if (m.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
this.timestamp = Optional.of(new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).parse(m.group(1)));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
this.timestamp = Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public URI getPath()
|
||||
{
|
||||
return this.basePath.resolve(dirName);
|
||||
}
|
||||
|
||||
String getDirName()
|
||||
{
|
||||
return dirName;
|
||||
}
|
||||
|
||||
public Optional<Date> getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OldBackupDirectory that)
|
||||
{
|
||||
if (this.timestamp.isPresent() && that.timestamp.isPresent())
|
||||
{
|
||||
return that.timestamp.get().compareTo(this.timestamp.get());
|
||||
}
|
||||
// Use absolute value of path in case the time-stamp is missing on either side.
|
||||
return that.getPath().compareTo(this.getPath());
|
||||
}
|
||||
}
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* Modification copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
package org.alfresco.solr.handler;
|
||||
|
||||
import org.apache.lucene.index.IndexCommit;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.SolrException.ErrorCode;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.core.DirectoryFactory.DirContext;
|
||||
import org.apache.solr.core.IndexDeletionPolicyWrapper;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.core.backup.repository.BackupRepository;
|
||||
import org.apache.solr.core.backup.repository.BackupRepository.PathType;
|
||||
import org.apache.solr.core.backup.repository.LocalFileSystemRepository;
|
||||
import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager;
|
||||
import org.apache.solr.search.SolrIndexSearcher;
|
||||
import org.apache.solr.util.RefCounted;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* <p> Provides functionality equivalent to the snapshooter script </p>
|
||||
* This is no longer used in standard replication.
|
||||
*
|
||||
* @since solr 1.4
|
||||
*/
|
||||
public class SnapShooter
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
private SolrCore solrCore;
|
||||
private String snapshotName = null;
|
||||
private String directoryName = null;
|
||||
private URI baseSnapDirPath = null;
|
||||
private URI snapshotDirPath = null;
|
||||
private BackupRepository backupRepo = null;
|
||||
private String commitName; // can be null
|
||||
|
||||
@Deprecated
|
||||
public SnapShooter(SolrCore core, String location, String snapshotName)
|
||||
{
|
||||
String snapDirStr;
|
||||
// Note - This logic is only applicable to the usecase where a shared file-system is exposed via
|
||||
// local file-system interface (primarily for backwards compatibility). For other use-cases, users
|
||||
// will be required to specify "location" where the backup should be stored.
|
||||
if (location == null)
|
||||
{
|
||||
snapDirStr = core.getDataDir();
|
||||
}
|
||||
else
|
||||
{
|
||||
snapDirStr = core.getCoreDescriptor().getInstanceDir().resolve(location).normalize().toString();
|
||||
}
|
||||
initialize(new LocalFileSystemRepository(), core, Paths.get(snapDirStr).toUri(), snapshotName, null);
|
||||
}
|
||||
|
||||
SnapShooter(BackupRepository backupRepo, SolrCore core, URI location, String snapshotName, String commitName)
|
||||
{
|
||||
initialize(backupRepo, core, location, snapshotName, commitName);
|
||||
}
|
||||
|
||||
private void initialize(BackupRepository backupRepo, SolrCore core, URI location, String snapshotName, String commitName)
|
||||
{
|
||||
this.solrCore = Objects.requireNonNull(core);
|
||||
this.backupRepo = Objects.requireNonNull(backupRepo);
|
||||
this.baseSnapDirPath = location;
|
||||
this.snapshotName = snapshotName;
|
||||
if (snapshotName != null)
|
||||
{
|
||||
directoryName = "snapshot." + snapshotName;
|
||||
}
|
||||
else
|
||||
{
|
||||
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.ROOT);
|
||||
directoryName = "snapshot." + fmt.format(new Date());
|
||||
}
|
||||
this.snapshotDirPath = backupRepo.resolve(location, directoryName);
|
||||
this.commitName = commitName;
|
||||
}
|
||||
|
||||
public BackupRepository getBackupRepository()
|
||||
{
|
||||
return backupRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent directory of the snapshots. This is the {@code location}
|
||||
* given in the constructor.
|
||||
*/
|
||||
public URI getLocation()
|
||||
{
|
||||
return this.baseSnapDirPath;
|
||||
}
|
||||
|
||||
void validateDeleteSnapshot()
|
||||
{
|
||||
Objects.requireNonNull(this.snapshotName);
|
||||
|
||||
boolean dirFound = false;
|
||||
String[] paths;
|
||||
try
|
||||
{
|
||||
paths = backupRepo.listAll(baseSnapDirPath);
|
||||
for (String path : paths)
|
||||
{
|
||||
if (path.equals(this.directoryName)
|
||||
&& backupRepo.getPathType(baseSnapDirPath.resolve(path)) == PathType.DIRECTORY)
|
||||
{
|
||||
dirFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dirFound)
|
||||
{
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||
"Snapshot " + snapshotName + " cannot be found in directory: " + baseSnapDirPath);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
||||
"Unable to find snapshot " + snapshotName + " in directory: " + baseSnapDirPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteSnapAsync(final AlfrescoReplicationHandler alfrescoReplicationHandler)
|
||||
{
|
||||
new Thread(() -> deleteNamedSnapshot(alfrescoReplicationHandler)).start();
|
||||
}
|
||||
|
||||
void validateCreateSnapshot() throws IOException
|
||||
{
|
||||
// Note - Removed the current behavior of creating the directory hierarchy.
|
||||
// Do we really need to provide this support?
|
||||
if (!backupRepo.exists(baseSnapDirPath))
|
||||
{
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||
" Directory does not exist: " + snapshotDirPath);
|
||||
}
|
||||
|
||||
if (backupRepo.exists(snapshotDirPath))
|
||||
{
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||
"Snapshot directory already exists: " + snapshotDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
public NamedList createSnapshot() throws Exception
|
||||
{
|
||||
RefCounted<SolrIndexSearcher> searcher = solrCore.getSearcher();
|
||||
try
|
||||
{
|
||||
if (commitName != null)
|
||||
{
|
||||
SolrSnapshotMetaDataManager snapshotMgr = solrCore.getSnapshotMetaDataManager();
|
||||
Optional<IndexCommit> commit = snapshotMgr.getIndexCommitByName(commitName);
|
||||
if (commit.isPresent())
|
||||
{
|
||||
return createSnapshot(commit.get());
|
||||
}
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR,
|
||||
"Unable to find an index commit with name " + commitName + " for core " + solrCore.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO should we try solrCore.getDeletionPolicy().getLatestCommit() first?
|
||||
IndexDeletionPolicyWrapper deletionPolicy = solrCore.getDeletionPolicy();
|
||||
IndexCommit indexCommit = searcher.get().getIndexReader().getIndexCommit();
|
||||
deletionPolicy.saveCommitPoint(indexCommit.getGeneration());
|
||||
try
|
||||
{
|
||||
return createSnapshot(indexCommit);
|
||||
}
|
||||
finally
|
||||
{
|
||||
deletionPolicy.releaseCommitPoint(indexCommit.getGeneration());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
searcher.decref();
|
||||
}
|
||||
}
|
||||
|
||||
void createSnapAsync(final IndexCommit indexCommit, final int numberToKeep, Consumer<NamedList> result)
|
||||
{
|
||||
solrCore.getDeletionPolicy().saveCommitPoint(indexCommit.getGeneration());
|
||||
|
||||
new Thread(() -> {
|
||||
try
|
||||
{
|
||||
result.accept(createSnapshot(indexCommit));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.error("Exception while creating snapshot", e);
|
||||
NamedList snapShootDetails = new NamedList<>();
|
||||
snapShootDetails.add("snapShootException", e.getMessage());
|
||||
result.accept(snapShootDetails);
|
||||
}
|
||||
finally
|
||||
{
|
||||
solrCore.getDeletionPolicy().releaseCommitPoint(indexCommit.getGeneration());
|
||||
}
|
||||
if (snapshotName == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
deleteOldBackups(numberToKeep);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Unable to delete old snapshots ", e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
// note: remember to reserve the indexCommit first so it won't get deleted concurrently
|
||||
private NamedList createSnapshot(final IndexCommit indexCommit) throws Exception
|
||||
{
|
||||
LOG.info("Creating backup snapshot " + (snapshotName == null ? "<not named>" : snapshotName) + " at "
|
||||
+ baseSnapDirPath);
|
||||
boolean success = false;
|
||||
try
|
||||
{
|
||||
NamedList<Object> details = new NamedList<>();
|
||||
details.add("startTime", new Date().toString());//bad; should be Instant.now().toString()
|
||||
|
||||
Collection<String> files = indexCommit.getFileNames();
|
||||
Directory dir = solrCore.getDirectoryFactory()
|
||||
.get(solrCore.getIndexDir(), DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
|
||||
try
|
||||
{
|
||||
for (String fileName : files)
|
||||
{
|
||||
backupRepo.copyFileFrom(dir, fileName, snapshotDirPath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
solrCore.getDirectoryFactory().release(dir);
|
||||
}
|
||||
|
||||
details.add("fileCount", files.size());
|
||||
details.add("status", "success");
|
||||
details.add("snapshotCompletedAt", new Date().toString());//bad; should be Instant.now().toString()
|
||||
details.add("snapshotName", snapshotName);
|
||||
LOG.info("Done creating backup snapshot: " + (snapshotName == null ? "<not named>" : snapshotName) + " at "
|
||||
+ baseSnapDirPath);
|
||||
success = true;
|
||||
return details;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
try
|
||||
{
|
||||
backupRepo.deleteDirectory(snapshotDirPath);
|
||||
}
|
||||
catch (Exception excDuringDelete)
|
||||
{
|
||||
LOG.warn("Failed to delete " + snapshotDirPath + " after snapshot creation failed due to: "
|
||||
+ excDuringDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteOldBackups(int numberToKeep) throws IOException
|
||||
{
|
||||
String[] paths = backupRepo.listAll(baseSnapDirPath);
|
||||
List<OldBackupDirectory> dirs = new ArrayList<>();
|
||||
for (String f : paths)
|
||||
{
|
||||
if (backupRepo.getPathType(baseSnapDirPath.resolve(f)) == PathType.DIRECTORY)
|
||||
{
|
||||
OldBackupDirectory obd = new OldBackupDirectory(baseSnapDirPath, f);
|
||||
if (obd.getTimestamp().isPresent())
|
||||
{
|
||||
dirs.add(obd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numberToKeep > dirs.size() - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Collections.sort(dirs);
|
||||
int i = 1;
|
||||
for (OldBackupDirectory dir : dirs)
|
||||
{
|
||||
if (i++ > numberToKeep)
|
||||
{
|
||||
backupRepo.deleteDirectory(dir.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void deleteNamedSnapshot(AlfrescoReplicationHandler alfrescoReplicationHandler)
|
||||
{
|
||||
LOG.info("Deleting snapshot: " + snapshotName);
|
||||
|
||||
NamedList<Object> details = new NamedList<>();
|
||||
|
||||
try
|
||||
{
|
||||
URI path = baseSnapDirPath.resolve("snapshot." + snapshotName);
|
||||
backupRepo.deleteDirectory(path);
|
||||
|
||||
details.add("status", "success");
|
||||
details.add("snapshotDeletedAt", new Date().toString());
|
||||
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
details.add("status", "Unable to delete snapshot: " + snapshotName);
|
||||
LOG.warn("Unable to delete snapshot: " + snapshotName, e);
|
||||
}
|
||||
|
||||
alfrescoReplicationHandler.snapShootDetails = details;
|
||||
}
|
||||
|
||||
private static final String DATE_FMT = "yyyyMMddHHmmssSSS";
|
||||
|
||||
}
|
||||
+5
-4
@@ -35,7 +35,7 @@ import org.alfresco.solr.tracker.CommitTracker;
|
||||
import org.alfresco.solr.tracker.ContentTracker;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.ModelTracker;
|
||||
import org.alfresco.solr.tracker.SlaveNodeStatePublisher;
|
||||
import org.alfresco.solr.tracker.SlaveCoreStatePublisher;
|
||||
import org.alfresco.solr.tracker.SolrTrackerScheduler;
|
||||
import org.alfresco.solr.tracker.Tracker;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
@@ -113,7 +113,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
AlfrescoSolrDataModel.getInstance().getDictionaryService(CMISStrictDictionaryService.DEFAULT),
|
||||
AlfrescoSolrDataModel.getInstance().getNamespaceDAO());
|
||||
|
||||
SolrContentStore contentStore = new SolrContentStore(coreContainer.getSolrHome());
|
||||
SolrContentStore contentStore = admin.getSolrContentStore();
|
||||
SolrInformationServer informationServer = new SolrInformationServer(admin, core, repositoryClient, contentStore);
|
||||
coreProperties.putAll(informationServer.getProps());
|
||||
admin.getInformationServers().put(core.getName(), informationServer);
|
||||
@@ -162,6 +162,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
|
||||
boolean trackersHaveBeenEnabled = Boolean.parseBoolean(coreProperties.getProperty("enable.alfresco.tracking", "true"));
|
||||
boolean owningCoreIsSlave = isSlaveModeEnabledFor(core);
|
||||
contentStore.toggleReadOnlyMode(owningCoreIsSlave);
|
||||
|
||||
if (trackerRegistry.hasTrackersForCore(core.getName()))
|
||||
{
|
||||
@@ -182,7 +183,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
{
|
||||
LOGGER.info("SearchServices Core Trackers have been explicitly disabled on core \"{}\" through \"enable.alfresco.tracking\" configuration property.", core.getName());
|
||||
|
||||
SlaveNodeStatePublisher statePublisher = new SlaveNodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
SlaveCoreStatePublisher statePublisher = new SlaveCoreStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
trackerRegistry.register(core.getName(), statePublisher);
|
||||
scheduler.schedule(statePublisher, core.getName(), coreProperties);
|
||||
trackers.add(statePublisher);
|
||||
@@ -197,7 +198,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
{
|
||||
LOGGER.info("SearchServices Core Trackers have been disabled on core \"{}\" because it is a slave core.", core.getName());
|
||||
|
||||
SlaveNodeStatePublisher statePublisher = new SlaveNodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
SlaveCoreStatePublisher statePublisher = new SlaveCoreStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
trackerRegistry.register(core.getName(), statePublisher);
|
||||
scheduler.schedule(statePublisher, core.getName(), coreProperties);
|
||||
trackers.add(statePublisher);
|
||||
|
||||
+18
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -49,11 +51,10 @@ public abstract class AbstractTracker implements Tracker
|
||||
protected SOLRAPIClient client;
|
||||
InformationServer infoSrv;
|
||||
protected String coreName;
|
||||
protected StoreRef storeRef;
|
||||
protected long batchCount;
|
||||
protected String alfrescoVersion;
|
||||
protected TrackerStats trackerStats;
|
||||
protected boolean runPostModelLoadInit = true;
|
||||
StoreRef storeRef;
|
||||
long batchCount;
|
||||
TrackerStats trackerStats;
|
||||
boolean runPostModelLoadInit = true;
|
||||
private int maxLiveSearchers;
|
||||
private volatile boolean shutdown = false;
|
||||
|
||||
@@ -63,9 +64,9 @@ public abstract class AbstractTracker implements Tracker
|
||||
protected volatile TrackerState state;
|
||||
protected int shardCount;
|
||||
protected int shardInstance;
|
||||
protected String shardMethod;
|
||||
String shardMethod;
|
||||
protected boolean transformContent;
|
||||
protected String shardTemplate;
|
||||
String shardTemplate;
|
||||
protected volatile boolean rollback;
|
||||
protected final Type type;
|
||||
|
||||
@@ -102,12 +103,8 @@ public abstract class AbstractTracker implements Tracker
|
||||
transformContent = Boolean.parseBoolean(p.getProperty("alfresco.index.transformContent", "true"));
|
||||
|
||||
this.trackerStats = this.infoSrv.getTrackerStats();
|
||||
|
||||
alfrescoVersion = p.getProperty("alfresco.version", "5.0.0");
|
||||
|
||||
this.type = type;
|
||||
|
||||
LOGGER.info("Solr built for Alfresco version: {}", alfrescoVersion);
|
||||
}
|
||||
|
||||
|
||||
@@ -183,12 +180,9 @@ public abstract class AbstractTracker implements Tracker
|
||||
|
||||
if(this.state == null)
|
||||
{
|
||||
/*
|
||||
* Set the global state for the tracker here.
|
||||
*/
|
||||
this.state = getTrackerState();
|
||||
LOGGER.debug("##### Setting tracker global state.");
|
||||
LOGGER.debug("State set: {}", this.state.toString());
|
||||
|
||||
LOGGER.debug("Global Tracker State set to: {}", this.state.toString());
|
||||
this.state.setRunning(true);
|
||||
}
|
||||
else
|
||||
@@ -237,12 +231,11 @@ public abstract class AbstractTracker implements Tracker
|
||||
finally
|
||||
{
|
||||
infoSrv.unregisterTrackerThread();
|
||||
if(state != null)
|
||||
{
|
||||
//During a rollback state is set to null.
|
||||
ofNullable(state).ifPresent(tstate -> {
|
||||
// During a rollback state is set to null.
|
||||
state.setRunning(false);
|
||||
state.setCheck(false);
|
||||
}
|
||||
});
|
||||
runLock.release();
|
||||
}
|
||||
}
|
||||
@@ -284,7 +277,7 @@ public abstract class AbstractTracker implements Tracker
|
||||
/**
|
||||
* Allows time for the scheduled asynchronous tasks to complete
|
||||
*/
|
||||
protected synchronized void waitForAsynchronous()
|
||||
synchronized void waitForAsynchronous()
|
||||
{
|
||||
AbstractWorkerRunnable currentRunnable = this.threadHandler.peekHeadReindexWorker();
|
||||
while (currentRunnable != null)
|
||||
@@ -305,12 +298,12 @@ public abstract class AbstractTracker implements Tracker
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxLiveSearchers()
|
||||
int getMaxLiveSearchers()
|
||||
{
|
||||
return maxLiveSearchers;
|
||||
}
|
||||
|
||||
protected void checkShutdown()
|
||||
void checkShutdown()
|
||||
{
|
||||
if(shutdown)
|
||||
{
|
||||
@@ -345,20 +338,11 @@ public abstract class AbstractTracker implements Tracker
|
||||
return this.writeLock;
|
||||
}
|
||||
|
||||
public Semaphore getRunLock()
|
||||
Semaphore getRunLock()
|
||||
{
|
||||
return this.runLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alfresco version Solr was built for
|
||||
*/
|
||||
@Override
|
||||
public String getAlfrescoVersion()
|
||||
{
|
||||
return alfrescoVersion;
|
||||
}
|
||||
|
||||
public Properties getProps()
|
||||
{
|
||||
return props;
|
||||
|
||||
+2
@@ -151,6 +151,8 @@ public class CommitTracker extends AbstractTracker
|
||||
maintenance();
|
||||
}
|
||||
|
||||
infoSrv.flushContentStore();
|
||||
|
||||
//Do the commit opening the searcher if needed. This will commit all the work done by indexing trackers.
|
||||
//This will return immediately and not wait for searchers to warm
|
||||
//System.out.println("################### Commit:"+openSearcherNeeded);
|
||||
|
||||
+30
-3
@@ -41,6 +41,7 @@ import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.solr.AlfrescoCoreAdminHandler;
|
||||
import org.alfresco.solr.AlfrescoSolrDataModel;
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.NodeReport;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -59,7 +60,7 @@ import java.util.Properties;
|
||||
* @since 1.5
|
||||
* @see <a href="https://issues.alfresco.com/jira/browse/SEARCH-1752">SEARCH-1752</a>
|
||||
*/
|
||||
public abstract class NodeStatePublisher extends AbstractTracker
|
||||
public abstract class CoreStatePublisher extends AbstractTracker
|
||||
{
|
||||
DocRouter docRouter;
|
||||
private final boolean isMaster;
|
||||
@@ -70,7 +71,7 @@ public abstract class NodeStatePublisher extends AbstractTracker
|
||||
/** The property to use for determining the shard. */
|
||||
protected Optional<QName> shardProperty = Optional.empty();
|
||||
|
||||
NodeStatePublisher(
|
||||
CoreStatePublisher(
|
||||
boolean isMaster,
|
||||
Properties p,
|
||||
SOLRAPIClient client,
|
||||
@@ -88,12 +89,28 @@ public abstract class NodeStatePublisher extends AbstractTracker
|
||||
docRouter = DocRouterFactory.getRouter(p, ShardMethodEnum.getShardMethod(shardMethod));
|
||||
}
|
||||
|
||||
NodeStatePublisher(Type type)
|
||||
CoreStatePublisher(Type type)
|
||||
{
|
||||
super(type);
|
||||
this.isMaster = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*
|
||||
* @param dbid the node identifier.
|
||||
* @return the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*/
|
||||
public NodeReport checkNode(Long dbid)
|
||||
{
|
||||
NodeReport nodeReport = new NodeReport();
|
||||
nodeReport.setDbid(dbid);
|
||||
|
||||
this.infoSrv.addCommonNodeReportInfo(nodeReport);
|
||||
|
||||
return nodeReport;
|
||||
}
|
||||
|
||||
private void firstUpdateShardProperty()
|
||||
{
|
||||
shardKey.ifPresent( shardKeyName -> {
|
||||
@@ -222,4 +239,14 @@ public abstract class NodeStatePublisher extends AbstractTracker
|
||||
{
|
||||
return this.docRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the hosting core is master or standalone.
|
||||
*
|
||||
* @return true if the hosting core is master or standalone.
|
||||
*/
|
||||
public boolean isOnMasterOrStandalone()
|
||||
{
|
||||
return isMaster;
|
||||
}
|
||||
}
|
||||
+9
-25
@@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory;
|
||||
* This tracks two things: transactions and metadata nodes
|
||||
* @author Ahmed Owian
|
||||
*/
|
||||
public class MetadataTracker extends NodeStatePublisher implements Tracker
|
||||
public class MetadataTracker extends CoreStatePublisher implements Tracker
|
||||
{
|
||||
protected final static Logger log = LoggerFactory.getLogger(MetadataTracker.class);
|
||||
private static final int DEFAULT_TRANSACTION_DOCS_BATCH_SIZE = 100;
|
||||
@@ -888,24 +888,18 @@ public class MetadataTracker extends NodeStatePublisher implements Tracker
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public NodeReport checkNode(Long dbid)
|
||||
{
|
||||
NodeReport nodeReport = new NodeReport();
|
||||
nodeReport.setDbid(dbid);
|
||||
NodeReport nodeReport = super.checkNode(dbid);
|
||||
|
||||
// In DB
|
||||
|
||||
GetNodesParameters parameters = new GetNodesParameters();
|
||||
parameters.setFromNodeId(dbid);
|
||||
parameters.setToNodeId(dbid);
|
||||
List<Node> dbnodes;
|
||||
try
|
||||
{
|
||||
dbnodes = client.getNodes(parameters, 1);
|
||||
List<Node> dbnodes = client.getNodes(parameters, 1);
|
||||
if (dbnodes.size() == 1)
|
||||
{
|
||||
Node dbnode = dbnodes.get(0);
|
||||
@@ -915,41 +909,31 @@ public class MetadataTracker extends NodeStatePublisher implements Tracker
|
||||
else
|
||||
{
|
||||
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
|
||||
nodeReport.setDbTx(-1l);
|
||||
nodeReport.setDbTx(-1L);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
|
||||
nodeReport.setDbTx(-2l);
|
||||
nodeReport.setDbTx(-2L);
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
|
||||
nodeReport.setDbTx(-3l);
|
||||
nodeReport.setDbTx(-3L);
|
||||
}
|
||||
catch (AuthenticationException e1)
|
||||
{
|
||||
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
|
||||
nodeReport.setDbTx(-4l);
|
||||
nodeReport.setDbTx(-4L);
|
||||
}
|
||||
|
||||
this.infoSrv.addCommonNodeReportInfo(nodeReport);
|
||||
|
||||
return nodeReport;
|
||||
}
|
||||
|
||||
public NodeReport checkNode(Node node)
|
||||
{
|
||||
NodeReport nodeReport = new NodeReport();
|
||||
nodeReport.setDbid(node.getId());
|
||||
|
||||
nodeReport.setDbNodeStatus(node.getStatus());
|
||||
nodeReport.setDbTx(node.getTxnId());
|
||||
|
||||
this.infoSrv.addCommonNodeReportInfo(nodeReport);
|
||||
|
||||
return nodeReport;
|
||||
return checkNode(node.getId());
|
||||
}
|
||||
|
||||
public List<Node> getFullNodesForDbTransaction(Long txid)
|
||||
|
||||
+28
-4
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import static org.alfresco.solr.tracker.Tracker.Type.NODE_STATE_PUBLISHER;
|
||||
@@ -14,8 +32,8 @@ import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Despite belonging to the Tracker ecosystem, this component is actually a publisher, which periodically informs
|
||||
* Alfresco about the state of the hosting slave node.
|
||||
* As the name suggests, this worker is scheduled only when the hosting node acts as a slave.
|
||||
* Alfresco about the state of the hosting slave core.
|
||||
* As the name suggests, this worker is scheduled only when the owning core acts as a slave.
|
||||
* It allows Solr's master/slave setup to be used with dynamic shard registration.
|
||||
*
|
||||
* In this scenario the slave is polling a "tracking" Solr node. The tracker below calls
|
||||
@@ -27,9 +45,9 @@ import java.util.Properties;
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
public class SlaveNodeStatePublisher extends NodeStatePublisher
|
||||
public class SlaveCoreStatePublisher extends CoreStatePublisher
|
||||
{
|
||||
public SlaveNodeStatePublisher(
|
||||
public SlaveCoreStatePublisher(
|
||||
boolean isMaster,
|
||||
Properties coreProperties,
|
||||
SOLRAPIClient repositoryClient,
|
||||
@@ -61,6 +79,12 @@ public class SlaveNodeStatePublisher extends NodeStatePublisher
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnMasterOrStandalone()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaintenance()
|
||||
{
|
||||
@@ -32,8 +32,6 @@ public interface Tracker
|
||||
boolean hasMaintenance() throws Exception;
|
||||
|
||||
Semaphore getWriteLock();
|
||||
|
||||
String getAlfrescoVersion();
|
||||
|
||||
void setShutdown(boolean shutdown);
|
||||
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.utils;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* @author Elia Porciani
|
||||
*/
|
||||
public class AlfrescoFileUtils {
|
||||
|
||||
/**
|
||||
* Check if two directories contains the same files
|
||||
*
|
||||
* @param dir
|
||||
* @param dir2
|
||||
* @param extensions Limits the search to the extensions provided
|
||||
* @param recursive Check recursively in all subdirs
|
||||
* @return
|
||||
*/
|
||||
public static boolean areDirectoryEquals(Path dir, Path dir2, String[] extensions, boolean recursive) {
|
||||
|
||||
Map<String, File> filesDir1 = FileUtils.listFiles(new File(dir.toUri()), extensions, recursive)
|
||||
.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
Map<String, File> filesDir2 = FileUtils.listFiles(new File(dir2.toUri()), extensions, recursive)
|
||||
.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
|
||||
if (filesDir1.size() != filesDir2.size())
|
||||
return false;
|
||||
|
||||
return filesDir1.entrySet().stream().allMatch(e -> {
|
||||
File fileDir2 = filesDir2.get(e.getKey());
|
||||
if (fileDir2 == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
byte[] otherBytes = Files.readAllBytes(e.getValue().toPath());
|
||||
byte[] thisBytes = Files.readAllBytes(fileDir2.toPath());
|
||||
|
||||
return (Arrays.equals(otherBytes, thisBytes));
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class Utils
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
/**
|
||||
* Returns the same input collection if that is not null, otherwise a new empty collection.
|
||||
* Provides a safe way for iterating over a returned collection (which could be null).
|
||||
*
|
||||
* @param values the collection.
|
||||
* @param <T> the collection type.
|
||||
* @return the same input collection if that is not null, otherwise a new empty collection.
|
||||
*/
|
||||
public static <T> Collection<T> notNullOrEmpty(Collection<T> values)
|
||||
{
|
||||
return values != null ? values : Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given input in an Integer, otherwise it returns null.
|
||||
*
|
||||
* @param value the numeric string.
|
||||
* @return the corresponding Integer or null in case the input is NaN.
|
||||
*/
|
||||
public static Integer toIntOrNull(String value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.valueOf(value);
|
||||
}
|
||||
catch(NumberFormatException nfe)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently closes the given {@link Closeable} resource without raising any exception.
|
||||
* This utility method is specifically useful when we have to close a resource in a lamba statement: since the
|
||||
* close() method could throw an {@link IOException} the compiler requires an enclosing try / catch block which
|
||||
* makes the code less readable.
|
||||
*
|
||||
* <br/><br/>
|
||||
* <p>
|
||||
* <code>
|
||||
* try { if (resource != null) resource.close } catch (IOException exception) { ... }
|
||||
* </code>
|
||||
* </p>
|
||||
* <br/>
|
||||
*
|
||||
* In these contexts a call to this method reduces the amount of code needed:
|
||||
*
|
||||
* <br/><br/>
|
||||
* <p>
|
||||
* <code>
|
||||
* silentlyClose(resource);
|
||||
* </code>
|
||||
* </p>
|
||||
*
|
||||
* @param resource the {@link Closeable} resource we want to silently close.
|
||||
*/
|
||||
public static void silentyClose(Closeable resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (resource != null) resource.close();
|
||||
}
|
||||
catch(IOException ignore)
|
||||
{
|
||||
LOGGER.warn("Unable to properly close the resource instance {}. See the stacktrace below for further details.", resource, ignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -1154,7 +1154,9 @@
|
||||
|
||||
https://wiki.apache.org/solr/SolrCloud/
|
||||
-->
|
||||
<requestHandler name="/replication" class="solr.ReplicationHandler" >
|
||||
|
||||
<requestHandler name="/replication" class="org.alfresco.solr.handler.AlfrescoReplicationHandler">
|
||||
|
||||
<!--
|
||||
To enable simple master/slave replication, uncomment one of the
|
||||
sections below, depending on whether this solr instance should be
|
||||
|
||||
+51
-83
@@ -33,7 +33,6 @@ import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.request.LocalSolrQueryRequest;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -83,8 +82,9 @@ import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_VERSI
|
||||
* class hierarchy. Ideally this function should be retired in favour of better
|
||||
* annotations..
|
||||
*
|
||||
* @since solr 1.5
|
||||
* @author Michael Suzuki
|
||||
* @since solr 1.4.1
|
||||
* @author Michael Suzuki
|
||||
* @author Andrea Gazzarini
|
||||
*/
|
||||
@ThreadLeakLingering(linger = 5000)
|
||||
public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
@@ -92,8 +92,8 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
protected static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected String[] deadServers;
|
||||
protected static SolrResponsesComparator solrComparator = new SolrResponsesComparator();
|
||||
protected static RandomSupplier solrRandomSupplier;
|
||||
protected static SolrResponsesComparator SOLR_RESPONSE_COMPARATOR = new SolrResponsesComparator();
|
||||
protected static RandomSupplier SOLR_RANDOM_SUPPLIER;
|
||||
|
||||
// to stress with higher thread counts and requests, make sure the junit
|
||||
// xml formatter is not being used (all output will be buffered before
|
||||
@@ -112,34 +112,26 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
* Test configs may use the <code>${hostContext}</code> variable to access
|
||||
* this system property.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@BeforeClass
|
||||
public static void setUpSolrTestProperties()
|
||||
{
|
||||
SOLR_RANDOM_SUPPLIER = new RandomSupplier();
|
||||
System.setProperty("alfresco.test", "true");
|
||||
System.setProperty("solr.tests.maxIndexingThreads", "10");
|
||||
System.setProperty("solr.tests.ramBufferSizeMB", "1024");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupPerTest()
|
||||
{
|
||||
this.solrRandomSupplier = new RandomSupplier();
|
||||
}
|
||||
|
||||
|
||||
public static String[] fieldNames = new String[]
|
||||
{ "n_ti1", "n_f1", "n_tf1", "n_d1", "n_td1", "n_l1", "n_tl1", "n_dt1", "n_tdt1" };
|
||||
|
||||
public static final String[] FIELD_NAMES = new String[] { "n_ti1", "n_f1", "n_tf1", "n_d1", "n_td1", "n_l1", "n_tl1", "n_dt1", "n_tdt1" };
|
||||
|
||||
protected static String[] getFieldNames()
|
||||
{
|
||||
return fieldNames;
|
||||
return FIELD_NAMES;
|
||||
}
|
||||
|
||||
protected void putHandleDefaults() {
|
||||
solrComparator.putHandleDefaults();
|
||||
protected static void putHandleDefaults()
|
||||
{
|
||||
SOLR_RESPONSE_COMPARATOR.putHandleDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,8 +139,6 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
* @param query - query to execute
|
||||
* @param count - min number of results each shard must satisfy
|
||||
* @param waitMillis - total ms to wait
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static boolean checkMinCountPerShard(Query query, int count, long waitMillis) throws SolrServerException,IOException
|
||||
{
|
||||
@@ -160,7 +150,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
for (SolrClient singleShard : shardedClients)
|
||||
{
|
||||
allShardCompliant = false;
|
||||
int totalHits = 0;
|
||||
int totalHits;
|
||||
int cycles = 1;
|
||||
while ((new Date()).getTime() < timeout && (!allShardCompliant))
|
||||
{
|
||||
@@ -172,11 +162,11 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
}
|
||||
try
|
||||
{
|
||||
Thread.sleep((long) (500 * cycles++));
|
||||
Thread.sleep(500 * cycles++);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
continue;
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,11 +195,6 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
/**
|
||||
* Waits until all cores (including shards) reach a count.
|
||||
*
|
||||
* @param query
|
||||
* @param count
|
||||
* @param waitMillis
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void waitForDocCountAllCores(Query query, int count, long waitMillis) throws Exception
|
||||
{
|
||||
@@ -230,12 +215,9 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
/**
|
||||
* Delele by query on all Clients
|
||||
*
|
||||
* @param q
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void deleteByQueryAllClients(String q) throws Exception {
|
||||
|
||||
public static void deleteByQueryAllClients(String q) throws Exception
|
||||
{
|
||||
List<SolrClient> clients = getStandaloneAndShardedClients();
|
||||
|
||||
for (SolrClient client : clients) {
|
||||
@@ -245,14 +227,11 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
/**
|
||||
* Gets the Default test client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected static SolrClient getDefaultTestClient()
|
||||
{
|
||||
return solrCollectionNameToStandaloneClient.get(DEFAULT_TEST_CORENAME);
|
||||
}
|
||||
|
||||
|
||||
protected static List<SolrClient> getShardedClients()
|
||||
{
|
||||
@@ -266,7 +245,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
*/
|
||||
public static List<SolrClient> getStandaloneAndShardedClients()
|
||||
{
|
||||
List<SolrClient> clients = new ArrayList();
|
||||
List<SolrClient> clients = new ArrayList<>();
|
||||
clients.addAll(solrCollectionNameToStandaloneClient.values());
|
||||
clients.addAll(clientShards);
|
||||
return clients;
|
||||
@@ -275,17 +254,11 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
public static List<SolrClient> getStandaloneClients()
|
||||
{
|
||||
List<SolrClient> clients = new ArrayList();
|
||||
clients.addAll(solrCollectionNameToStandaloneClient.values());
|
||||
return clients;
|
||||
return new ArrayList<>(solrCollectionNameToStandaloneClient.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the doc count on the first core available, then checks all the Shards match.
|
||||
* @param query
|
||||
* @param count
|
||||
* @param waitMillis
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void waitForDocCount(Query query, int count, long waitMillis) throws Exception
|
||||
{
|
||||
@@ -296,6 +269,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
{
|
||||
long begin = System.currentTimeMillis();
|
||||
//TODO: Support multiple cores per jetty
|
||||
System.out.println(getStandaloneClients().size());
|
||||
SolrClient standaloneClient = getStandaloneClients().get(0); //Get the first one
|
||||
waitForDocCountCore(standaloneClient, query, count, waitMillis, begin);
|
||||
waitForShardsCount(query, count, waitMillis, begin);
|
||||
@@ -331,7 +305,8 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
return escapedField + ":" + value + " ";
|
||||
}
|
||||
|
||||
protected static String escapeQueryChars(String query) {
|
||||
protected static String escapeQueryChars(String query)
|
||||
{
|
||||
return query.replaceAll("\\:", "\\\\:")
|
||||
.replaceAll("\\{", "\\\\{")
|
||||
.replaceAll("\\}", "\\\\}");
|
||||
@@ -340,7 +315,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
public static SolrQuery luceneToSolrQuery(Query query)
|
||||
{
|
||||
String[] terms = query.toString().split(" ");
|
||||
String escapedQuery = new String();
|
||||
String escapedQuery = "";
|
||||
for (String t : terms)
|
||||
{
|
||||
escapedQuery += escapeQueryClause(t);
|
||||
@@ -365,8 +340,10 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
waitForShardsCount(solrQuery, count, waitMillis, start);
|
||||
}
|
||||
|
||||
protected static void injectDocToShards(long txnId, long aclId, long dbId, String owner) throws Exception {
|
||||
for(SolrClient clientShard : clientShards) {
|
||||
protected static void injectDocToShards(long txnId, long aclId, long dbId, String owner) throws Exception
|
||||
{
|
||||
for(SolrClient clientShard : clientShards)
|
||||
{
|
||||
SolrInputDocument doc = new SolrInputDocument();
|
||||
String id = AlfrescoSolrDataModel.getNodeDocumentId(AlfrescoSolrDataModel.DEFAULT_TENANT, aclId, dbId);
|
||||
doc.addField(FIELD_SOLR4_ID, id);
|
||||
@@ -382,21 +359,20 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
/**
|
||||
* Gets the cores for the jetty instances
|
||||
* @return
|
||||
*/
|
||||
protected static List<SolrCore> getJettyCores(Collection<JettySolrRunner> runners)
|
||||
{
|
||||
List<SolrCore> cores = new ArrayList();
|
||||
List<SolrCore> cores = new ArrayList<>();
|
||||
for (JettySolrRunner jettySolrRunner : runners)
|
||||
{
|
||||
jettySolrRunner.getCoreContainer().getCores().forEach(aCore -> cores.add(aCore));
|
||||
cores.addAll(jettySolrRunner.getCoreContainer().getCores());
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
protected static List<AlfrescoCoreAdminHandler> getAdminHandlers(Collection<JettySolrRunner> runners)
|
||||
{
|
||||
List<AlfrescoCoreAdminHandler> coreAdminHandlers = new ArrayList();
|
||||
List<AlfrescoCoreAdminHandler> coreAdminHandlers = new ArrayList<>();
|
||||
for (JettySolrRunner jettySolrRunner : runners)
|
||||
{
|
||||
CoreContainer coreContainer = jettySolrRunner.getCoreContainer();
|
||||
@@ -417,32 +393,27 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
List<SolrClient> clients = getShardedClients();
|
||||
SolrQuery query = luceneToSolrQuery(new TermQuery(new Term(FIELD_DOC_TYPE, SolrInformationServer.DOC_TYPE_NODE)));
|
||||
StringBuilder error = new StringBuilder();
|
||||
for (int i = 0; i < clients.size(); ++i)
|
||||
for (SolrClient client : clients)
|
||||
{
|
||||
SolrClient client = clients.get(i);
|
||||
|
||||
|
||||
QueryResponse response = client.query(query);
|
||||
int totalHits = (int) response.getResults().getNumFound();
|
||||
|
||||
|
||||
if(totalHits > 0)
|
||||
if (totalHits > 0)
|
||||
{
|
||||
shardHit++;
|
||||
}
|
||||
if(totalHits < count)
|
||||
|
||||
if (totalHits < count)
|
||||
{
|
||||
if (ignoreZero && totalHits == 0)
|
||||
{
|
||||
log.info(client+": have zero hits ");
|
||||
}
|
||||
else
|
||||
{
|
||||
error.append(" "+client+": ");
|
||||
error.append("Expected nodes per shard greater than "+count+" found "+totalHits+" : "+query.toString());
|
||||
if (ignoreZero && totalHits == 0) {
|
||||
log.info(client + ": have zero hits ");
|
||||
} else {
|
||||
error.append(" " + client + ": ");
|
||||
error.append("Expected nodes per shard greater than " + count + " found " + totalHits + " : " + query.toString());
|
||||
}
|
||||
}
|
||||
log.info(client+": Hits "+totalHits);
|
||||
log.info(client + ": Hits " + totalHits);
|
||||
|
||||
}
|
||||
|
||||
@@ -559,7 +530,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
protected String getShardsString()
|
||||
{
|
||||
Random r = solrRandomSupplier.getRandomGenerator();
|
||||
Random r = SOLR_RANDOM_SUPPLIER.getRandomGenerator();
|
||||
if (deadServers == null)
|
||||
return shards;
|
||||
|
||||
@@ -609,7 +580,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
protected static SolrInputDocument addRandFields(SolrInputDocument sdoc)
|
||||
{
|
||||
addFields(sdoc, solrRandomSupplier.getRandFields(getFieldNames(), solrRandomSupplier.getRandValues()));
|
||||
addFields(sdoc, SOLR_RANDOM_SUPPLIER.getRandFields(getFieldNames(), SOLR_RANDOM_SUPPLIER.getRandValues()));
|
||||
return sdoc;
|
||||
}
|
||||
|
||||
@@ -661,7 +632,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
{
|
||||
UpdateResponse controlRsp = add(client1, params, sdocs);
|
||||
UpdateResponse specificRsp = add(client2, params, sdocs);
|
||||
solrComparator.compareSolrResponses(specificRsp, controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.compareSolrResponses(specificRsp, controlRsp);
|
||||
return specificRsp;
|
||||
}
|
||||
|
||||
@@ -723,9 +694,6 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
/**
|
||||
* * Commits to the specified client, and optionally all shards
|
||||
* @param client
|
||||
* @param andShards
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static void commit(SolrClient client, boolean andShards) throws Exception
|
||||
{
|
||||
@@ -742,7 +710,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
protected static QueryResponse queryRandomShard(ModifiableSolrParams params) throws SolrServerException, IOException
|
||||
{
|
||||
Random r = solrRandomSupplier.getRandomGenerator();
|
||||
Random r = SOLR_RANDOM_SUPPLIER.getRandomGenerator();
|
||||
int which = r.nextInt(clientShards.size());
|
||||
SolrClient client = clientShards.get(which);
|
||||
QueryResponse rsp = client.query(params);
|
||||
@@ -754,13 +722,13 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
params.set("distrib", "false");
|
||||
QueryRequest request = getAlfrescoRequest(json, params);
|
||||
QueryResponse controlRsp = request.process(solrClient);
|
||||
solrComparator.validateResponse(controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.validateResponse(controlRsp);
|
||||
if (andShards)
|
||||
{
|
||||
params.remove("distrib");
|
||||
setDistributedParams(params);
|
||||
QueryResponse rsp = queryRandomShard(json, params);
|
||||
solrComparator.compareResponses(rsp, controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.compareResponses(rsp, controlRsp);
|
||||
return rsp;
|
||||
}
|
||||
else
|
||||
@@ -771,7 +739,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
protected static QueryResponse queryRandomShard(String json, SolrParams params) throws SolrServerException, IOException
|
||||
{
|
||||
Random r = solrRandomSupplier.getRandomGenerator();
|
||||
Random r = SOLR_RANDOM_SUPPLIER.getRandomGenerator();
|
||||
int which = r.nextInt(clientShards.size());
|
||||
SolrClient client = clientShards.get(which);
|
||||
QueryRequest request = getAlfrescoRequest(json, params);
|
||||
@@ -790,13 +758,13 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
*/
|
||||
protected QueryResponse query(SolrClient solrClient, boolean setDistribParams, SolrParams p) throws Exception
|
||||
{
|
||||
Random r = solrRandomSupplier.getRandomGenerator();
|
||||
Random r = SOLR_RANDOM_SUPPLIER.getRandomGenerator();
|
||||
final ModifiableSolrParams params = new ModifiableSolrParams(p);
|
||||
|
||||
// TODO: look into why passing true causes fails
|
||||
params.set("distrib", "false");
|
||||
final QueryResponse controlRsp = solrClient.query(params);
|
||||
solrComparator.validateResponse(controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.validateResponse(controlRsp);
|
||||
|
||||
params.remove("distrib");
|
||||
if (setDistribParams)
|
||||
@@ -804,7 +772,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
|
||||
QueryResponse rsp = queryRandomShard(params);
|
||||
|
||||
solrComparator.compareResponses(rsp, controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.compareResponses(rsp, controlRsp);
|
||||
|
||||
if (stress > 0)
|
||||
{
|
||||
@@ -826,7 +794,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
QueryResponse rsp = client.query(new ModifiableSolrParams(params));
|
||||
if (verifyStress)
|
||||
{
|
||||
solrComparator.compareResponses(rsp, controlRsp);
|
||||
SOLR_RESPONSE_COMPARATOR.compareResponses(rsp, controlRsp);
|
||||
}
|
||||
} catch (SolrServerException | IOException e)
|
||||
{
|
||||
@@ -864,7 +832,7 @@ public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
|
||||
first = rsp;
|
||||
} else
|
||||
{
|
||||
solrComparator.compareResponses(first, rsp);
|
||||
SOLR_RESPONSE_COMPARATOR.compareResponses(first, rsp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-9
@@ -33,6 +33,7 @@ import org.apache.chemistry.opencmis.commons.impl.json.JSONValue;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.hadoop.util.Time;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
@@ -66,6 +67,7 @@ import org.xml.sax.SAXException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Paths;
|
||||
@@ -96,6 +98,12 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
private static Log LOG = LogFactory.getLog(AbstractAlfrescoSolrIT.class);
|
||||
private static boolean CORE_NOT_YET_CREATED = true;
|
||||
|
||||
|
||||
private static String testExecutionSolrHome;
|
||||
private static String testSolrCollection;
|
||||
private static String testSolrConf;
|
||||
private static String templateConf;
|
||||
|
||||
/**
|
||||
* Harness initialized by initTestHarness.
|
||||
* <p/>
|
||||
@@ -118,6 +126,20 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
protected static NodeRef TEST_NODEREF;
|
||||
protected static Date FTS_TEST_DATE;
|
||||
|
||||
|
||||
|
||||
protected static void copyTestFiles() throws IOException {
|
||||
|
||||
//Add solr home conf folder with alfresco based configuration.
|
||||
|
||||
File testExecutionFolder = Paths.get(testExecutionSolrHome).toFile();
|
||||
|
||||
for (String s : List.of("/conf", "/alfrescoModels", "/templates", "/collection1")) {
|
||||
FileUtils.copyDirectory(Paths.get(TEST_FILES_LOCATION + s).toFile(), Paths.get(testExecutionFolder + s).toFile());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Bunch of methods that wrap testHarness object usage.
|
||||
* TestHarness is a class for internal solr test and should not be use outside Solr.
|
||||
* Unfortunately this class is used in too many test and a complete refactor would have a huge impact
|
||||
@@ -217,6 +239,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
@Deprecated
|
||||
public static void initAlfrescoCore(String schema) throws Exception
|
||||
{
|
||||
|
||||
LOG.info("##################################### init Alfresco core ##############");
|
||||
LOG.info("####initCore");
|
||||
System.setProperty("solr.solr.home", TEST_FILES_LOCATION);
|
||||
@@ -230,18 +253,29 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
System.setProperty("alfresco.test", "true");
|
||||
System.setProperty("solr.tests.mergeScheduler", "org.apache.lucene.index.ConcurrentMergeScheduler");
|
||||
System.setProperty("solr.tests.mergePolicy", "org.apache.lucene.index.TieredMergePolicy");
|
||||
|
||||
if (CORE_NOT_YET_CREATED)
|
||||
{
|
||||
|
||||
testExecutionSolrHome = TEST_EXECUTION_FOLDER + "/" + Time.now() + "/solrhome";
|
||||
testSolrCollection = testExecutionSolrHome + "/collection1";
|
||||
testSolrConf = testSolrCollection + "/conf/";
|
||||
templateConf = testExecutionSolrHome + "/templates/%s/conf/";
|
||||
|
||||
copyTestFiles();
|
||||
createAlfrescoCore(schema);
|
||||
}
|
||||
LOG.info("####initCore end");
|
||||
|
||||
|
||||
|
||||
admin = of(h).map(TestHarness::getCoreContainer)
|
||||
.map(CoreContainer::getMultiCoreHandler)
|
||||
.map(AlfrescoCoreAdminHandler.class::cast)
|
||||
.orElseThrow(RuntimeException::new);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated as testHarness is used
|
||||
*/
|
||||
@@ -262,11 +296,11 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
{
|
||||
String templateName = System.getProperty("templateName", "rerank");
|
||||
FileUtils.copyFile(
|
||||
Paths.get(String.format(TEMPLATE_CONF, templateName) + schema).toFile(),
|
||||
Paths.get(TEST_SOLR_CONF + schema).toFile());
|
||||
Paths.get(String.format(templateConf, templateName) + schema).toFile(),
|
||||
Paths.get(testSolrConf + schema).toFile());
|
||||
}
|
||||
|
||||
SolrResourceLoader resourceLoader = new SolrResourceLoader(Paths.get(TEST_FILES_LOCATION), null, properties);
|
||||
SolrResourceLoader resourceLoader = new SolrResourceLoader(Paths.get(testExecutionSolrHome), null, properties);
|
||||
TestCoresLocator locator = new TestCoresLocator(SolrTestCaseJ4.DEFAULT_TEST_CORENAME,
|
||||
"data",
|
||||
"solrconfig.xml",
|
||||
@@ -291,9 +325,9 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
}
|
||||
|
||||
@AfterClass()
|
||||
public static void tearDown()
|
||||
{
|
||||
public static void tearDown() throws IOException {
|
||||
h.close();
|
||||
FileUtils.forceDelete(Paths.get(TEST_EXECUTION_FOLDER).toFile());
|
||||
CORE_NOT_YET_CREATED = true;
|
||||
}
|
||||
/**
|
||||
@@ -308,7 +342,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
/**
|
||||
* Validates an update XML String is successful
|
||||
*/
|
||||
public void assertU(String update)
|
||||
public void assertU(String update)
|
||||
{
|
||||
assertU(null, update);
|
||||
}
|
||||
@@ -324,7 +358,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
/**
|
||||
* Validates an update XML String failed
|
||||
*/
|
||||
public void assertFailedU(String update)
|
||||
public void assertFailedU(String update)
|
||||
{
|
||||
assertFailedU(null, update);
|
||||
}
|
||||
@@ -433,6 +467,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
e2.printStackTrace();
|
||||
throw new RuntimeException("Exception during query", e2);
|
||||
}
|
||||
}
|
||||
@@ -497,7 +532,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
throws Exception
|
||||
{
|
||||
Date date = new Date();
|
||||
long timeout = (long)date.getTime() + waitMillis;
|
||||
long timeout = date.getTime() + waitMillis;
|
||||
|
||||
RefCounted<SolrIndexSearcher> ref = null;
|
||||
int totalHits = 0;
|
||||
@@ -737,6 +772,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
finally
|
||||
@@ -784,7 +820,7 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
{
|
||||
public SolrServletRequest(SolrCore core, HttpServletRequest req)
|
||||
{
|
||||
super(core, new MultiMapSolrParams(Collections.<String, String[]> emptyMap()));
|
||||
super(core, new MultiMapSolrParams(Collections.emptyMap()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class AdminHandlerDistributedIT extends AbstractAlfrescoDistributedIT
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.alfresco.solr.AlfrescoCollatableMLTextFieldType.MLTextSortFieldComparator;
|
||||
import org.apache.lucene.index.BinaryDocValues;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
/** Unit tests for {@link AlfrescoCollatableMLTextFieldType}. */
|
||||
public class AlfrescoCollatableMLTextFieldTypeTest
|
||||
{
|
||||
private static final int NUM_HITS = 3;
|
||||
private static final String FIELD = "field";
|
||||
private static final Locale LOCALE = Locale.getDefault();
|
||||
/** A document id. */
|
||||
private static final int DOC = 0;
|
||||
/** A value for the current bottom document. */
|
||||
private static final String BOTTOM_STRING = "Bottom";
|
||||
|
||||
@InjectMocks
|
||||
MLTextSortFieldComparator textSortFieldComparator = new MLTextSortFieldComparator(NUM_HITS, FIELD, LOCALE);
|
||||
@Mock
|
||||
BinaryDocValues mockDocTerms;
|
||||
@Mock
|
||||
Bits mockDocsWithField;
|
||||
@Mock
|
||||
Collator mockCollator;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
initMocks(this);
|
||||
reset(mockDocTerms, mockDocsWithField);
|
||||
textSortFieldComparator.bottom = BOTTOM_STRING;
|
||||
}
|
||||
|
||||
/** Check that a zero length term is sorted before a populated field. */
|
||||
@Test
|
||||
public void testCompareBottom_termLengthZeroAndDocDoesntHaveField()
|
||||
{
|
||||
// Set up the document to have an empty term.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef());
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Call the method under test.
|
||||
int result = textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
assertEquals("Expected value for doc to be null, and so it to be sorted before BOTTOM_TERM", 1, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the behaviour of compareBottom when docsWithField is null (this happens when all documents contain the
|
||||
* field).
|
||||
*/
|
||||
@Test
|
||||
public void testCompareBottom_nullDocsWithField()
|
||||
{
|
||||
// Set docsWithField to null to simulate all documents containing the field.
|
||||
Bits oldValue = textSortFieldComparator.docsWithField;
|
||||
textSortFieldComparator.docsWithField = null;
|
||||
|
||||
// Set up the document to have an empty term.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef());
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
// Expect the EMPTY_TERM to be compared
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "");
|
||||
|
||||
// Reset docsWithField with the mock after the test.
|
||||
textSortFieldComparator.docsWithField = oldValue;
|
||||
}
|
||||
|
||||
/** Check that if the doc has a value then it is compared with the existing value. */
|
||||
@Test
|
||||
public void testCompareBottom_populatedTerm()
|
||||
{
|
||||
// Set up the document to have "Some value" for the field.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef("Some value"));
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "Some value");
|
||||
}
|
||||
|
||||
/** Check the behaviour if the multilanguage term is encoded. */
|
||||
@Test
|
||||
public void testCompareBottom_encodedTerm_localeFound()
|
||||
{
|
||||
// Create an encoded multilanguage string with Russian, US English and Thai with Thai digits.
|
||||
String mlText = "\u0000ru\u0000First\u0000Ignored" +
|
||||
"\u0000en_US\u0000Second\u0000IgnoredToo" +
|
||||
"\u0000th_TH_TH\u0000Third\u0000AlsoIgnored";
|
||||
// Set up the document to have an encoded value for the field.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef(mlText));
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Check that the Russian text can be extracted.
|
||||
textSortFieldComparator.collatorLocale = Locale.forLanguageTag("ru");
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "First");
|
||||
|
||||
// Check that the English text can be extracted.
|
||||
textSortFieldComparator.collatorLocale = Locale.forLanguageTag("en");
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "Second");
|
||||
|
||||
// Check that the Thai text can be extracted.
|
||||
textSortFieldComparator.collatorLocale = Locale.forLanguageTag("th");
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "Third");
|
||||
|
||||
// Reset the locale for other tests.
|
||||
textSortFieldComparator.collatorLocale = LOCALE;
|
||||
}
|
||||
|
||||
/** Check the behaviour if the term has a locale but no text. */
|
||||
@Test
|
||||
public void testCompareBottom_badlyEncodedTerm()
|
||||
{
|
||||
// Set the value to have a locale but no text.
|
||||
String mlText = "\u0000ru";
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef(mlText));
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
// Check that an empty string is assumed.
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_nullLessThanString()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues(null, "NotNull");
|
||||
assertEquals("Expected null to be 'less' than string.", -1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_stringGreaterThanNull()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues("NotNull", null);
|
||||
assertEquals("Expected string to be 'greater' than null.", 1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_nullEqualToNull()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues(null, null);
|
||||
assertEquals("Expected two null values to be equal.", 0, result);
|
||||
}
|
||||
|
||||
/** Check that when two non-null strings are compared then the underlying collator is used to get the result. */
|
||||
@Test
|
||||
public void testCompareValues_twoStringsCompared()
|
||||
{
|
||||
// An arbitrary value to be returned by the collator.
|
||||
int comparisonResult = 10;
|
||||
when(mockCollator.compare("NotNull1", "NotNull2")).thenReturn(comparisonResult);
|
||||
|
||||
// Call the method under test.
|
||||
int result = textSortFieldComparator.compareValues("NotNull1", "NotNull2");
|
||||
|
||||
verify(mockCollator).compare("NotNull1", "NotNull2");
|
||||
assertEquals("Expected result to be obtained from collator.", comparisonResult, result);
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.alfresco.solr.AlfrescoCollatableTextFieldType.TextSortFieldComparator;
|
||||
import org.apache.lucene.index.BinaryDocValues;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
/** Unit tests for {@link AlfrescoCollatableTextFieldType}. */
|
||||
public class AlfrescoCollatableTextFieldTypeTest
|
||||
{
|
||||
private static final int NUM_HITS = 3;
|
||||
private static final String FIELD = "field";
|
||||
private static final Locale LOCALE = Locale.getDefault();
|
||||
/** A document id. */
|
||||
private static final int DOC = 0;
|
||||
/** A value for the current bottom document. */
|
||||
private static final String BOTTOM_STRING = "Bottom";
|
||||
|
||||
@InjectMocks
|
||||
TextSortFieldComparator textSortFieldComparator = new TextSortFieldComparator(NUM_HITS, FIELD, LOCALE);
|
||||
@Mock
|
||||
BinaryDocValues mockDocTerms;
|
||||
@Mock
|
||||
Bits mockDocsWithField;
|
||||
@Mock
|
||||
Collator mockCollator;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
initMocks(this);
|
||||
reset(mockDocTerms, mockDocsWithField);
|
||||
textSortFieldComparator.bottom = BOTTOM_STRING;
|
||||
}
|
||||
|
||||
/** Check that a zero length term is sorted before a populated field. */
|
||||
@Test
|
||||
public void testCompareBottom_termLengthZeroAndDocDoesntHaveField()
|
||||
{
|
||||
// Set up the document to have an empty term.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef());
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Call the method under test.
|
||||
int result = textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
assertEquals("Expected value for doc to be null, and so it to be sorted before BOTTOM_TERM", 1, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the behaviour of compareBottom when docsWithField is null (this happens when all documents contain the
|
||||
* field).
|
||||
*/
|
||||
@Test
|
||||
public void testCompareBottom_nullDocsWithField()
|
||||
{
|
||||
// Set docsWithField to null to simulate all documents containing the field.
|
||||
Bits oldValue = textSortFieldComparator.docsWithField;
|
||||
textSortFieldComparator.docsWithField = null;
|
||||
|
||||
// Set up the document to have an empty term.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef());
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
// Expect the EMPTY_TERM to be compared
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "");
|
||||
|
||||
// Reset docsWithField with the mock after the test.
|
||||
textSortFieldComparator.docsWithField = oldValue;
|
||||
}
|
||||
|
||||
/** Check that if the doc has a value then it is compared with the existing value. */
|
||||
@Test
|
||||
public void testCompareBottom_populatedTerm()
|
||||
{
|
||||
// Set up the document to have "Some value" for the field.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef("Some value"));
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "Some value");
|
||||
}
|
||||
|
||||
/** Check the behaviour if the term is encoded. */
|
||||
@Test
|
||||
public void testCompareBottom_encodedTerm()
|
||||
{
|
||||
// Set up the document to have an encoded value for the field.
|
||||
when(mockDocTerms.get(DOC)).thenReturn(new BytesRef("\u0000Value\u0000Ignored"));
|
||||
when(mockDocsWithField.get(DOC)).thenReturn(false);
|
||||
|
||||
// Call the method under test.
|
||||
textSortFieldComparator.compareBottom(DOC);
|
||||
|
||||
verify(mockCollator).compare(BOTTOM_STRING, "Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_nullLessThanString()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues(null, "NotNull");
|
||||
assertEquals("Expected null to be 'less' than string.", -1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_stringGreaterThanNull()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues("NotNull", null);
|
||||
assertEquals("Expected string to be 'greater' than null.", 1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareValues_nullEqualToNull()
|
||||
{
|
||||
int result = textSortFieldComparator.compareValues(null, null);
|
||||
assertEquals("Expected two null values to be equal.", 0, result);
|
||||
}
|
||||
|
||||
/** Check that when two non-null strings are compared then the underlying collator is used to get the result. */
|
||||
@Test
|
||||
public void testCompareValues_twoStringsCompared()
|
||||
{
|
||||
// An arbitrary value to be returned by the collator.
|
||||
int comparisonResult = 10;
|
||||
when(mockCollator.compare("NotNull1", "NotNull2")).thenReturn(comparisonResult);
|
||||
|
||||
// Call the method under test.
|
||||
int result = textSortFieldComparator.compareValues("NotNull1", "NotNull2");
|
||||
|
||||
verify(mockCollator).compare("NotNull1", "NotNull2");
|
||||
assertEquals("Expected result to be obtained from collator.", comparisonResult, result);
|
||||
}
|
||||
}
|
||||
+178
-9
@@ -20,15 +20,23 @@ package org.alfresco.solr;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ALFRESCO_CORE_NAME;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARCHIVE_CORE_NAME;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_TXID;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.STORE_REF_MAP;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.VERSION_CORE_NAME;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -42,13 +50,18 @@ import java.util.stream.Collectors;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.solr.adapters.IOpenBitSet;
|
||||
import org.alfresco.solr.tracker.AclTracker;
|
||||
import org.alfresco.solr.tracker.DocRouter;
|
||||
import org.alfresco.solr.tracker.IndexHealthReport;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.PropertyRouter;
|
||||
import org.alfresco.solr.tracker.SlaveCoreStatePublisher;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.CoreAdminParams;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.junit.Before;
|
||||
@@ -105,6 +118,169 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
when(req.getParams()).thenReturn(params);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithEmptyParameter_shouldReturnAnEmptyList()
|
||||
{
|
||||
assertTrue(alfrescoCoreAdminHandler.extractShards("", Integer.MAX_VALUE).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithNullParameter_shouldReturnAnEmptyList()
|
||||
{
|
||||
assertTrue(alfrescoCoreAdminHandler.extractShards(null, Integer.MAX_VALUE).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithOneInvalidShard_shouldReturnAnEmptyList()
|
||||
{
|
||||
assertTrue(alfrescoCoreAdminHandler.extractShards("This is an invalid shard id", Integer.MAX_VALUE).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithOneShards_shouldReturnSingletonList()
|
||||
{
|
||||
assertEquals(singletonList(1), alfrescoCoreAdminHandler.extractShards("1", Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithSeveralValidShards_shouldReturnAllOfThemInTheList()
|
||||
{
|
||||
assertEquals(asList(1,5,6,11,23), alfrescoCoreAdminHandler.extractShards("1,5,6,11,23", Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithSeveralValidShards_shouldReturnOnlyValidIdentifiers()
|
||||
{
|
||||
assertEquals(asList(1,5,6,11,23), alfrescoCoreAdminHandler.extractShards("1,5,A,6,xyz,11,BB,23,o01z", Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractShardsWithSeveralValidShardsAndLimit_shouldConsiderOnlyShardsLesserThanLimit()
|
||||
{
|
||||
assertEquals(asList(1,5,6,11,12), alfrescoCoreAdminHandler.extractShards("1,5,6,11,23,25,99,223,12", 23));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAlfrescoCoreWhenInputIsNull_shouldReturnFalse()
|
||||
{
|
||||
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAlfrescoCoreWhenWeHaveNoCore_shouldReturnFalse()
|
||||
{
|
||||
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAlfrescoCoreWhenDoesntHaveAnyTracker_shouldReturnFalse()
|
||||
{
|
||||
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAlfrescoCoreWithRegisteredTrackers_shouldReturnTrue()
|
||||
{
|
||||
when(trackerRegistry.hasTrackersForCore("CoreD")).thenReturn(true);
|
||||
assertTrue(alfrescoCoreAdminHandler.hasAlfrescoCore(asList(dummyCore("CoreA"), dummyCore("CoreB"), dummyCore("CoreC"), dummyCore("CoreD"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trackerRegistryHasNoCoreNames_itShouldReturnAnEmptyList()
|
||||
{
|
||||
assertTrue(alfrescoCoreAdminHandler.coreNames().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreDetectedAsMasterOrStandalone()
|
||||
{
|
||||
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
|
||||
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class)))
|
||||
.thenReturn(coreStatePublisher);
|
||||
|
||||
assertTrue(alfrescoCoreAdminHandler.isMasterOrStandalone("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreDetectedAsSlave()
|
||||
{
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(null);
|
||||
assertFalse(alfrescoCoreAdminHandler.isMasterOrStandalone("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsMaster_thenCoreStatePublisherInstanceCorrespondsToMetadataTracker()
|
||||
{
|
||||
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
|
||||
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class)))
|
||||
.thenReturn(coreStatePublisher);
|
||||
|
||||
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsSlave_thenCoreStatePublisherInstanceCorrespondsToSlaveCoreStatePublisher()
|
||||
{
|
||||
SlaveCoreStatePublisher coreStatePublisher = mock(SlaveCoreStatePublisher.class);
|
||||
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(null);
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(SlaveCoreStatePublisher.class))).thenReturn(coreStatePublisher);
|
||||
|
||||
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsSlave_thenDocRouterIsNull()
|
||||
{
|
||||
String coreName = "aCore";
|
||||
when(trackerRegistry.getTrackerForCore(eq(coreName), eq(MetadataTracker.class))).thenReturn(null);
|
||||
assertNull(alfrescoCoreAdminHandler.getDocRouter("aCore"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsMaster_thenDocRouterIsProperlyReturned()
|
||||
{
|
||||
DocRouter expectedRouter = new PropertyRouter("someProperty_.{1,35}");
|
||||
|
||||
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
|
||||
when(coreStatePublisher.getDocRouter()).thenReturn(expectedRouter);
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(coreStatePublisher);
|
||||
|
||||
assertSame(expectedRouter, alfrescoCoreAdminHandler.getDocRouter("aCore"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void targetCoreNameCanBeSpecifiedInSeveralWays()
|
||||
{
|
||||
String coreName = "ThisIsTheCoreName";
|
||||
|
||||
ModifiableSolrParams params = new ModifiableSolrParams();
|
||||
|
||||
assertNull(alfrescoCoreAdminHandler.coreName(params));
|
||||
|
||||
params.set(CoreAdminParams.CORE, coreName);
|
||||
|
||||
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
|
||||
|
||||
params.remove(CoreAdminParams.CORE);
|
||||
assertNull(alfrescoCoreAdminHandler.coreName(params));
|
||||
|
||||
params.set("coreName", coreName);
|
||||
|
||||
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
|
||||
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
|
||||
}
|
||||
|
||||
|
||||
private SolrCore dummyCore(String name)
|
||||
{
|
||||
SolrCore core = mock(SolrCore.class);
|
||||
when(core.getName()).thenReturn(name);
|
||||
return core;
|
||||
}
|
||||
|
||||
/** Check that a transaction report can be generated. */
|
||||
@Test
|
||||
public void handleCustomActionTXReportSuccess() throws Exception
|
||||
@@ -143,8 +319,6 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
public void handleCustomActionTXReportMissingTXId()
|
||||
{
|
||||
when(params.get(CoreAdminParams.ACTION)).thenReturn(TXREPORT);
|
||||
when(params.get(ARG_TXID)).thenReturn(null);
|
||||
|
||||
alfrescoCoreAdminHandler.handleCustomAction(req, rsp);
|
||||
|
||||
verify(rsp, never()).add(anyString(), any());
|
||||
@@ -156,11 +330,8 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
{
|
||||
when(params.get(CoreAdminParams.ACTION)).thenReturn(TXREPORT);
|
||||
when(params.get(CoreAdminParams.CORE)).thenReturn(null);
|
||||
when(params.get(ARG_TXID)).thenReturn(TX_ID);
|
||||
|
||||
alfrescoCoreAdminHandler.handleCustomAction(req, rsp);
|
||||
|
||||
verify(rsp, never()).add(anyString(), any());
|
||||
}
|
||||
|
||||
/** Check that when an unknown action is provided we don't generate a report. */
|
||||
@@ -212,10 +383,9 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
public void coreNamesAreTrimmed_oneCoreNameAtTime() {
|
||||
AlfrescoCoreAdminHandler spy = spy(new AlfrescoCoreAdminHandler() {
|
||||
@Override
|
||||
protected boolean newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
|
||||
protected void newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
|
||||
{
|
||||
// Do nothing here otherwise we cannot spy it
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -238,10 +408,9 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
public void validAndInvalidCoreNames() {
|
||||
AlfrescoCoreAdminHandler spy = spy(new AlfrescoCoreAdminHandler() {
|
||||
@Override
|
||||
protected boolean newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
|
||||
protected void newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
|
||||
{
|
||||
// Do nothing here otherwise we cannot spy it
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -217,9 +217,9 @@ public class AlfrescoSolrUtils
|
||||
nodeMetaData.setAclId(acl.getId());
|
||||
nodeMetaData.setTxnId(txn.getId());
|
||||
nodeMetaData.setOwner(owner);
|
||||
nodeMetaData.setAspects(new HashSet<QName>());
|
||||
nodeMetaData.setAspects(new HashSet<>());
|
||||
nodeMetaData.setAncestors(ancestors);
|
||||
Map<QName, PropertyValue> props = new HashMap<QName, PropertyValue>();
|
||||
Map<QName, PropertyValue> props = new HashMap<>();
|
||||
props.put(ContentModel.PROP_IS_INDEXED, new StringPropertyValue("true"));
|
||||
props.put(ContentModel.PROP_CONTENT, new ContentPropertyValue(Locale.US, 0l, "UTF-8", "text/plain", null));
|
||||
nodeMetaData.setProperties(props);
|
||||
@@ -230,8 +230,8 @@ public class AlfrescoSolrUtils
|
||||
}
|
||||
nodeMetaData.setType(QName.createQName(TEST_NAMESPACE, "testSuperType"));
|
||||
nodeMetaData.setAncestors(ancestors);
|
||||
nodeMetaData.setPaths(new ArrayList<Pair<String, QName>>());
|
||||
nodeMetaData.setNamePaths(new ArrayList<List<String>>());
|
||||
nodeMetaData.setPaths(new ArrayList<>());
|
||||
nodeMetaData.setNamePaths(new ArrayList<>());
|
||||
return nodeMetaData;
|
||||
}
|
||||
/**
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ public class CoresCreateUpdateDistributedIT extends AbstractAlfrescoDistributedI
|
||||
}
|
||||
|
||||
@After
|
||||
private void destroyData() throws Throwable
|
||||
private void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class CoresCreateViaPropertyIT extends AbstractAlfrescoDistributedIT
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
System.clearProperty(AlfrescoCoreAdminHandler.ALFRESCO_DEFAULTS);
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
|
||||
+73
-88
@@ -4,6 +4,7 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
|
||||
import org.alfresco.solr.basics.RandomSupplier;
|
||||
import org.alfresco.solr.client.SOLRAPIQueueClient;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.hadoop.util.Time;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.embedded.JettyConfig;
|
||||
@@ -90,8 +91,8 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
//Standalone Tests
|
||||
protected static SolrCore defaultCore;
|
||||
|
||||
protected static final int clientConnectionTimeout = DEFAULT_CONNECTION_TIMEOUT;;
|
||||
protected static final int clientSoTimeout = 90000;;
|
||||
protected static final int clientConnectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
|
||||
protected static final int clientSoTimeout = 90000;
|
||||
|
||||
protected static final String id = "id";
|
||||
|
||||
@@ -128,21 +129,22 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
jettyContainers = new HashMap<>();
|
||||
|
||||
nodeCnt = new AtomicInteger(0);
|
||||
String serverName = testClassName;
|
||||
currentTestName = serverName;
|
||||
currentTestName = testClassName;
|
||||
String[] coreNames = new String[]{DEFAULT_TEST_CORENAME};
|
||||
|
||||
distribSetUp(serverName);
|
||||
RandomSupplier.RandVal.uniqueValues = new HashSet(); // reset random values
|
||||
createServers(serverName, coreNames, numShards,solrcoreProperties);
|
||||
System.setProperty("solr.solr.home", testDir.toPath().resolve(serverName).toString());
|
||||
distribSetUp(testClassName);
|
||||
RandomSupplier.RandVal.uniqueValues = new HashSet<>(); // reset random values
|
||||
createServers(testClassName, coreNames, numShards,solrcoreProperties);
|
||||
System.setProperty("solr.solr.home", testDir.toPath().resolve(testClassName).toString());
|
||||
}
|
||||
|
||||
private static Properties addExplicitShardingProperty(Properties solrcoreProperties)
|
||||
{
|
||||
if(solrcoreProperties == null){
|
||||
if(solrcoreProperties == null)
|
||||
{
|
||||
solrcoreProperties = new Properties();
|
||||
}
|
||||
|
||||
if(solrcoreProperties.getProperty("shard.method")==null)
|
||||
{
|
||||
solrcoreProperties.put("shard.method", "EXPLICIT_ID");
|
||||
@@ -150,7 +152,8 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
return solrcoreProperties;
|
||||
}
|
||||
|
||||
public static void initSingleSolrServer(String testClassName, Properties solrcoreProperties) throws Throwable {
|
||||
public static void initSingleSolrServer(String testClassName, Properties solrcoreProperties) throws Throwable
|
||||
{
|
||||
initSolrServers(0,testClassName,solrcoreProperties);
|
||||
|
||||
JettySolrRunner jsr = jettyContainers.get(testClassName);
|
||||
@@ -162,7 +165,8 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
{
|
||||
int i = 0;
|
||||
extras = new String[solrcoreProperties.size()*2];
|
||||
for (Map.Entry<Object, Object> prop:solrcoreProperties.entrySet()) {
|
||||
for (Map.Entry<Object, Object> prop:solrcoreProperties.entrySet())
|
||||
{
|
||||
extras[i++] = "property."+prop.getKey();
|
||||
extras[i++] = (String) prop.getValue();
|
||||
}
|
||||
@@ -184,7 +188,7 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
destroyServers();
|
||||
distribTearDown();
|
||||
|
||||
boolean keepTests = Boolean.valueOf(System.getProperty("keep.tests"));
|
||||
boolean keepTests = Boolean.parseBoolean(System.getProperty("keep.tests"));
|
||||
if (!keepTests) FileUtils.deleteDirectory(testDir);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -202,7 +206,7 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
return System.getProperty("user.dir") + "/target/test-classes/test-files";
|
||||
}
|
||||
|
||||
public static void distribSetUp(String serverName) throws Exception
|
||||
public static void distribSetUp(String serverName)
|
||||
{
|
||||
SolrTestCaseJ4.resetExceptionIgnores(); // ignore anything with
|
||||
// ignore_exception in it
|
||||
@@ -212,7 +216,7 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
System.setProperty("solr.log.dir", testDir.toPath().resolve(serverName).toString());
|
||||
}
|
||||
|
||||
public static void distribTearDown() throws Exception
|
||||
public static void distribTearDown()
|
||||
{
|
||||
System.clearProperty("solr.directoryFactory");
|
||||
System.clearProperty("solr.log.dir");
|
||||
@@ -225,13 +229,12 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
SOLRAPIQueueClient.aclMap.clear();
|
||||
SOLRAPIQueueClient.nodeMap.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a JettySolrRunner (if one didn't exist already). DOES NOT START IT.
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private static JettySolrRunner createJetty(String jettyKey, boolean basicAuth) throws Exception
|
||||
protected static JettySolrRunner createJetty(String jettyKey, boolean basicAuth) throws Exception
|
||||
{
|
||||
if (jettyContainers.containsKey(jettyKey))
|
||||
{
|
||||
@@ -241,21 +244,15 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
{
|
||||
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
|
||||
seedSolrHome(jettySolrHome);
|
||||
JettySolrRunner jetty = createJetty(jettySolrHome.toFile(), null, null, false, getSchemaFile(), basicAuth);
|
||||
return jetty;
|
||||
return createJetty(jettySolrHome.toFile(), null, null, false, 0, getSchemaFile(), basicAuth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the core config information to the jetty file system.
|
||||
* Its best to call this before calling start() on Jetty
|
||||
* @param jettyKey
|
||||
* @param sourceConfigName
|
||||
* @param coreName
|
||||
* @param additionalProperties
|
||||
* @throws Exception
|
||||
*/
|
||||
private static void addCoreToJetty(String jettyKey, String sourceConfigName, String coreName, Properties additionalProperties) throws Exception
|
||||
protected static void addCoreToJetty(String jettyKey, String sourceConfigName, String coreName, Properties additionalProperties) throws Exception
|
||||
{
|
||||
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
|
||||
Path coreSourceConfig = new File(getTestFilesHome() + "/"+sourceConfigName).toPath();
|
||||
@@ -287,17 +284,14 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
out.close();
|
||||
in.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts jetty if its not already running
|
||||
* @param jsr
|
||||
* @throws Exception
|
||||
*/
|
||||
private static void startJetty(JettySolrRunner jsr) throws Exception {
|
||||
protected static void startJetty(JettySolrRunner jsr) throws Exception
|
||||
{
|
||||
if (!jsr.isRunning())
|
||||
{
|
||||
jsr.start();
|
||||
@@ -313,31 +307,35 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
if(additionalProperties != null && additionalProperties.size() > 0) {
|
||||
if(additionalProperties != null && additionalProperties.size() > 0)
|
||||
{
|
||||
properties.putAll(additionalProperties);
|
||||
properties.remove("shard.method");
|
||||
}
|
||||
|
||||
for (int i = 0; i < coreNames.length; i++)
|
||||
for (String coreName : coreNames)
|
||||
{
|
||||
addCoreToJetty(jettyKey, coreNames[i], coreNames[i], properties);
|
||||
addCoreToJetty(jettyKey, coreName, coreName, properties);
|
||||
}
|
||||
|
||||
//Now start jetty
|
||||
startJetty(jsr);
|
||||
|
||||
int jettyPort = jsr.getLocalPort();
|
||||
for (int i = 0; i < coreNames.length; i++)
|
||||
for (String coreName : coreNames)
|
||||
{
|
||||
String url = buildUrl(jettyPort) + "/" + coreNames[i];
|
||||
String url = buildUrl(jettyPort) + "/" + coreName;
|
||||
|
||||
log.info(url);
|
||||
solrCollectionNameToStandaloneClient.put(coreNames[i], createNewSolrClient(url));
|
||||
|
||||
solrCollectionNameToStandaloneClient.put(coreName, createNewSolrClient(url));
|
||||
}
|
||||
|
||||
shardsArr = new String[numShards];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (additionalProperties == null) {
|
||||
if (additionalProperties == null)
|
||||
{
|
||||
additionalProperties = new Properties();
|
||||
}
|
||||
String[] ranges = {"0-100", "100-200", "200-300", "300-400"};
|
||||
@@ -355,7 +353,7 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
props.put("shard.range", ranges[i]);
|
||||
}
|
||||
|
||||
String shardKey = jettyKey+"_shard_"+i;
|
||||
String shardKey = jettyKey+"_shard_" + i + "/solrhome";
|
||||
JettySolrRunner j = createJetty(shardKey, basicAuth);
|
||||
//use the first corename specified as the Share template
|
||||
addCoreToJetty(shardKey, coreNames[0], shardname, props);
|
||||
@@ -373,12 +371,13 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
|
||||
protected static void destroyServers() throws Exception
|
||||
{
|
||||
List<String> solrHomes = new ArrayList<String>();
|
||||
List<String> solrHomes = new ArrayList<>();
|
||||
for (JettySolrRunner jetty : jettyContainers.values())
|
||||
{
|
||||
solrHomes.add(jetty.getSolrHome());
|
||||
jetty.stop();
|
||||
}
|
||||
|
||||
for (SolrClient jClients : solrCollectionNameToStandaloneClient.values())
|
||||
{
|
||||
jClients.close();
|
||||
@@ -406,24 +405,16 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
solrCollectionNameToStandaloneClient.clear();
|
||||
}
|
||||
|
||||
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, String schemaOverride, boolean basicAuth) throws Exception
|
||||
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, int port, String schemaOverride, boolean basicAuth)
|
||||
{
|
||||
return createJetty(solrHome, dataDir, shardList, sslEnabled, schemaOverride, useExplicitNodeNames, basicAuth);
|
||||
return createJetty(solrHome, dataDir, shardList, sslEnabled, port, schemaOverride, useExplicitNodeNames, basicAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a solr jetty server.
|
||||
*
|
||||
* @param solrHome
|
||||
* @param dataDir
|
||||
* @param shardList
|
||||
* @param schemaOverride
|
||||
* @param explicitCoreNodeName
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled,
|
||||
String schemaOverride, boolean explicitCoreNodeName, boolean basicAuth) throws Exception
|
||||
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, int port,
|
||||
String schemaOverride, boolean explicitCoreNodeName, boolean basicAuth)
|
||||
{
|
||||
Properties props = new Properties();
|
||||
if (schemaOverride != null)
|
||||
@@ -439,20 +430,21 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
{
|
||||
props.setProperty("coreNodeName", Integer.toString(nodeCnt.incrementAndGet()));
|
||||
}
|
||||
|
||||
SSLConfig sslConfig = new SSLConfig(sslEnabled, false, null, null, null, null);
|
||||
|
||||
JettyConfig config = null;
|
||||
JettyConfig config;
|
||||
|
||||
if(basicAuth) {
|
||||
System.out.println("###### adding basic auth ######");
|
||||
config = JettyConfig.builder().setContext("/solr").withFilter(BasicAuthFilter.class, "/sql/*").stopAtShutdown(true).withSSLConfig(sslConfig).build();
|
||||
if(basicAuth)
|
||||
{
|
||||
log.info("###### adding basic auth ######");
|
||||
config = JettyConfig.builder().setContext("/solr").setPort(port).withFilter(BasicAuthFilter.class, "/sql/*").stopAtShutdown(true).withSSLConfig(sslConfig).build();
|
||||
} else {
|
||||
System.out.println("###### no basic auth ######");
|
||||
config = JettyConfig.builder().setContext("/solr").stopAtShutdown(true).withSSLConfig(sslConfig).build();
|
||||
log.info("###### no basic auth ######");
|
||||
config = JettyConfig.builder().setContext("/solr").setPort(port).stopAtShutdown(true).withSSLConfig(sslConfig).build();
|
||||
}
|
||||
|
||||
JettySolrRunner jetty = new JettySolrRunner(solrHome.getAbsolutePath(), props, config);
|
||||
return jetty;
|
||||
return new JettySolrRunner(solrHome.getAbsolutePath(), props, config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,11 +498,8 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
*/
|
||||
protected static void seedSolrHome(Path jettyHome) throws IOException
|
||||
{
|
||||
String solrxml = getSolrXml();
|
||||
if (solrxml != null)
|
||||
{
|
||||
FileUtils.copyFile(new File(getTestFilesHome(), solrxml), jettyHome.resolve(getSolrXml()).toFile());
|
||||
}
|
||||
FileUtils.copyFile(new File(getTestFilesHome(), getSolrXml()), jettyHome.resolve(getSolrXml()).toFile());
|
||||
|
||||
//Add solr home conf folder with alfresco based configuration.
|
||||
FileUtils.copyDirectory(new File(getTestFilesHome() + "/conf"), jettyHome.resolve("conf").toFile());
|
||||
// Add alfresco data model def
|
||||
@@ -543,36 +532,26 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
FileUtils.copyDirectory(coreSourceConfig.resolve("conf").toFile(), confDir.toFile());
|
||||
}
|
||||
|
||||
protected void setupJettySolrHome(String coreName, Path jettyHome) throws IOException
|
||||
public static class BasicAuthFilter implements Filter
|
||||
{
|
||||
seedSolrHome(jettyHome);
|
||||
|
||||
Properties coreProperties = new Properties();
|
||||
coreProperties.setProperty("name", coreName);
|
||||
coreProperties.setProperty("shard", "${shard:}");
|
||||
coreProperties.setProperty("collection", "${collection:" + coreName + "}");
|
||||
coreProperties.setProperty("config", "${solrconfig:solrconfig.xml}");
|
||||
coreProperties.setProperty("schema", "${schema:schema.xml}");
|
||||
coreProperties.setProperty("coreNodeName", "${coreNodeName:}");
|
||||
|
||||
writeCoreProperties(jettyHome.resolve("cores").resolve(coreName), coreProperties, coreName);
|
||||
}
|
||||
|
||||
public static class BasicAuthFilter implements Filter {
|
||||
|
||||
public BasicAuthFilter() {
|
||||
public BasicAuthFilter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void init(FilterConfig config) {
|
||||
public void init(FilterConfig config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
throws IOException, ServletException
|
||||
{
|
||||
//Parse the basic auth filter
|
||||
String auth = ((HttpServletRequest)request).getHeader("Authorization");
|
||||
if(auth != null) {
|
||||
if(auth != null)
|
||||
{
|
||||
auth = auth.replace("Basic ", "");
|
||||
byte[] bytes = Base64.getDecoder().decode(auth);
|
||||
String decodedBytes = new String(bytes);
|
||||
@@ -580,18 +559,24 @@ public abstract class SolrITInitializer extends SolrTestCaseJ4
|
||||
String user = pair[0];
|
||||
String password = pair[1];
|
||||
//Just look for the hard coded user and password.
|
||||
if (user.equals("test") && password.equals("pass")) {
|
||||
if (user.equals("test") && password.equals("pass"))
|
||||
{
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
public void destroy()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-75
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -19,18 +19,15 @@
|
||||
|
||||
package org.alfresco.solr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.alfresco.solr.content.SolrContentStore;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.params.CommonParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.apache.solr.request.LocalSolrQueryRequest;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.request.SolrRequestHandler;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
@@ -43,8 +40,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -99,6 +94,7 @@ public class SolrInformationServerTest
|
||||
request = infoServer.newSolrQueryRequest();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testGetStateOk()
|
||||
{
|
||||
@@ -124,6 +120,7 @@ public class SolrInformationServerTest
|
||||
/**
|
||||
* GetState returns null in case the given id doesn't correspond to an existing state document.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testGetStateWithStateNotFound_returnsNull()
|
||||
{
|
||||
@@ -143,73 +140,4 @@ public class SolrInformationServerTest
|
||||
|
||||
assertNull(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexAcl() throws IOException
|
||||
{
|
||||
/*
|
||||
// Source/expected data
|
||||
List<AclReaders> aclReadersList = new ArrayList<AclReaders>();
|
||||
aclReadersList.add(new AclReaders(101, Arrays.asList("r1", "r2", "r3"), Arrays.asList("d1", "d2"), 999,
|
||||
"example.com"));
|
||||
aclReadersList.add(new AclReaders(102, Arrays.asList("r4", "r5", "r6"), Arrays.asList("d3", "d4"), 999,
|
||||
"another.test"));
|
||||
aclReadersList.add(new AclReaders(103, Arrays.asList("GROUP_marketing", "simpleuser", "GROUP_EVERYONE",
|
||||
"ROLE_GUEST", "ROLE_ADMINISTRATOR", "ROLE_OWNER", "ROLE_RANDOM"), Arrays.asList(
|
||||
"GROUP_engineering", "justauser", "GROUP_EVERYONE", "ROLE_GUEST", "ROLE_ADMINISTRATOR",
|
||||
"ROLE_OWNER", "ROLE_RANDOM"), 999, "tenant.test"));
|
||||
aclReadersList.add(new AclReaders(104, Arrays.asList("GROUP_marketing", "simpleuser", "GROUP_EVERYONE",
|
||||
"ROLE_GUEST", "ROLE_ADMINISTRATOR", "ROLE_OWNER", "ROLE_RANDOM"), Arrays.asList(
|
||||
"GROUP_engineering", "justauser", "GROUP_EVERYONE", "ROLE_GUEST", "ROLE_ADMINISTRATOR",
|
||||
"ROLE_OWNER", "ROLE_RANDOM"), 999, "" Zero-length tenant == no mangling ));
|
||||
|
||||
//final boolean willOverwrite = true;
|
||||
|
||||
// Invoke the method under test
|
||||
//infoServer.indexAcl(aclReadersList, willOverwrite);
|
||||
|
||||
|
||||
// Capture the AddUpdateCommand instances for further analysis.
|
||||
//ArgumentCaptor<AddUpdateCommand> cmdArg = ArgumentCaptor.forClass(AddUpdateCommand.class);
|
||||
// The processor processes as many add commands as there are items in the aclReaderList.
|
||||
//verify(processor, times(aclReadersList.size())).processAdd(cmdArg.capture());
|
||||
|
||||
// Verify that the AddUpdateCommand is as expected.
|
||||
//List<AddUpdateCommand> updates = cmdArg.getAllValues();
|
||||
//assertEquals("Wrong number of updates", aclReadersList.size(), updates.size());
|
||||
/*
|
||||
for (int docIndex = 0; docIndex < updates.size(); docIndex++)
|
||||
{
|
||||
AddUpdateCommand update = updates.get(docIndex);
|
||||
assertEquals("Overwrite flag was not correct value.", willOverwrite, update.overwrite);
|
||||
SolrInputDocument inputDoc = update.getSolrInputDocument();
|
||||
// Retrieve the original AclReaders object and compare with data in submitted SolrInputDocument
|
||||
final AclReaders sourceAclReaders = aclReadersList.get(docIndex);
|
||||
assertEquals(
|
||||
AlfrescoSolrDataModel.getTenantId(sourceAclReaders.getTenantDomain()) + "!"
|
||||
+ NumericEncoder.encode(sourceAclReaders.getId()) + "!ACL",
|
||||
inputDoc.getFieldValue("id").toString());
|
||||
assertEquals("0", inputDoc.getFieldValue("_version_").toString());
|
||||
assertEquals(sourceAclReaders.getId(), inputDoc.getFieldValue(QueryConstants.FIELD_ACLID));
|
||||
assertEquals(sourceAclReaders.getAclChangeSetId(), inputDoc.getFieldValue(QueryConstants.FIELD_INACLTXID));
|
||||
|
||||
if (sourceAclReaders.getId() == 103)
|
||||
{
|
||||
// Authorities *may* (e.g. GROUP, EVERYONE, GUEST) be mangled to include tenant information
|
||||
final Collection<Object> docReaders = inputDoc.getFieldValues(QueryConstants.FIELD_READER);
|
||||
assertEquals(Arrays.asList("GROUP_marketing@tenant.test", "simpleuser", "GROUP_EVERYONE@tenant.test",
|
||||
"ROLE_GUEST@tenant.test", "ROLE_ADMINISTRATOR", "ROLE_OWNER", "ROLE_RANDOM"), docReaders);
|
||||
final Collection<Object> docDenied = inputDoc.getFieldValues(QueryConstants.FIELD_DENIED);
|
||||
assertEquals(Arrays.asList("GROUP_engineering@tenant.test", "justauser", "GROUP_EVERYONE@tenant.test",
|
||||
"ROLE_GUEST@tenant.test", "ROLE_ADMINISTRATOR", "ROLE_OWNER", "ROLE_RANDOM"), docDenied);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple case, no authority/tenant mangling.
|
||||
assertEquals(sourceAclReaders.getReaders(), inputDoc.getFieldValues(QueryConstants.FIELD_READER));
|
||||
assertEquals(sourceAclReaders.getDenied(), inputDoc.getFieldValues(QueryConstants.FIELD_DENIED));
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ package org.alfresco.solr;
|
||||
*/
|
||||
public interface SolrTestFiles
|
||||
{
|
||||
public final String TEST_FILES_LOCATION = "target/test-classes/test-files";
|
||||
public final String TEST_SOLR_COLLECTION = TEST_FILES_LOCATION + "/collection1";
|
||||
public final String TEST_SOLR_CONF = TEST_SOLR_COLLECTION + "/conf/";
|
||||
public final String TEMPLATE_CONF = TEST_FILES_LOCATION + "/templates/%s/conf/";
|
||||
String TEST_FILES_LOCATION = "target/test-classes/test-files";
|
||||
String TEST_EXECUTION_FOLDER = "target/test-execution-folder";
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class TemplatesDistributedIT extends AbstractAlfrescoDistributedIT
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Solr ContentStore {@link ChangeSet} test case.
|
||||
*
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SolrContentStoreChangeSetTest
|
||||
{
|
||||
private ChangeSet changeSet;
|
||||
private final String contentStoreRootFolder = "/tmp";
|
||||
private final File rootFolder = new File(contentStoreRootFolder, ChangeSet.CHANGESETS_ROOT_FOLDER_NAME);
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
changeSet = new ChangeSet.Builder().withContentStoreRoot(contentStoreRootFolder).build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException
|
||||
{
|
||||
changeSet.close();
|
||||
FileUtils.cleanDirectory(rootFolder);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nullContentStoreRootFolder_shouldThrowAnException()
|
||||
{
|
||||
new ChangeSet.Builder().withContentStoreRoot(null).build();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nonWriteableContentStoreRootFolder_shouldThrowAnException()
|
||||
{
|
||||
new ChangeSet.Builder().withContentStoreRoot("/root").build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newAddOrReplaceRecord_shouldRemovePreviousDeletion()
|
||||
{
|
||||
String path ="some/random/dbid.gz";
|
||||
|
||||
assertTrue(changeSet.deletes.isEmpty());
|
||||
assertTrue(changeSet.adds.isEmpty());
|
||||
|
||||
changeSet.delete(path);
|
||||
|
||||
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.contains(path));
|
||||
assertTrue(String.valueOf(changeSet.adds), changeSet.adds.isEmpty());
|
||||
|
||||
changeSet.addOrReplace(path);
|
||||
|
||||
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.isEmpty());
|
||||
assertTrue(String.valueOf(changeSet.adds),changeSet.adds.contains(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletedRecord_shouldRemovePreviousAdd()
|
||||
{
|
||||
String path ="some/random/dbid.gz";
|
||||
|
||||
assertTrue(changeSet.deletes.isEmpty());
|
||||
assertTrue(changeSet.adds.isEmpty());
|
||||
|
||||
changeSet.addOrReplace(path);
|
||||
|
||||
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.isEmpty());
|
||||
assertTrue(String.valueOf(changeSet.adds), changeSet.adds.contains(path));
|
||||
|
||||
changeSet.delete(path);
|
||||
|
||||
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.contains(path));
|
||||
assertTrue(String.valueOf(changeSet.adds),changeSet.adds.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transientChangeset_doesNothingOnFlush() throws IOException
|
||||
{
|
||||
ChangeSet changeset = new ChangeSet.Builder().build();
|
||||
changeset.addOrReplace("A");
|
||||
changeset.delete("B");
|
||||
|
||||
assertEquals(1, changeset.deletes.size());
|
||||
assertEquals(1, changeset.adds.size());
|
||||
|
||||
changeset.flush();
|
||||
|
||||
assertEquals(1, changeset.deletes.size());
|
||||
assertEquals(1, changeset.adds.size());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void emptyChangeset_isImmutableDoesntAllowAdds()
|
||||
{
|
||||
ChangeSet changeset = new ChangeSet.Builder().empty().build();
|
||||
changeset.addOrReplace("A");
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void emptyChangeset_isImmutableDoesntAllowDeletes()
|
||||
{
|
||||
ChangeSet changeset = new ChangeSet.Builder().empty().build();
|
||||
changeset.delete("A");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastCommittedVersionNotPresentAtVeryBeginning()
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastCommittedVersionNotAvailable_shouldReturnNO_AVAILABLE_VERSION() throws IOException
|
||||
{
|
||||
changeSet.selectEverything = mock(Query.class);
|
||||
when(changeSet.selectEverything.rewrite(any(IndexReader.class))).thenThrow(new RuntimeException());
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushDoesNothingIfThereAreNoChanges() throws IOException
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidOrUnknownVersion() throws IOException
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
|
||||
assertTrue(changeSet.isUnknownVersion(SolrContentStore.NO_VERSION_AVAILABLE));
|
||||
assertTrue(changeSet.isUnknownVersion(SolrContentStore.NO_VERSION_AVAILABLE - 1L));
|
||||
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
|
||||
|
||||
changeSet.addOrReplace("A1");
|
||||
changeSet.addOrReplace("A2");
|
||||
changeSet.delete("A3");
|
||||
changeSet.delete("A1");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
|
||||
assertNotEquals(SolrContentStore.NO_VERSION_AVAILABLE, lastCommittedVersionAfterFirstFlush);
|
||||
|
||||
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validVersion() throws IOException
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
|
||||
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
|
||||
|
||||
changeSet.addOrReplace("A1");
|
||||
changeSet.addOrReplace("A2");
|
||||
changeSet.delete("A3");
|
||||
changeSet.delete("A1");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
|
||||
|
||||
changeSet.addOrReplace("B1");
|
||||
changeSet.addOrReplace("B2");
|
||||
changeSet.delete("B3");
|
||||
changeSet.delete("B1");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersionAfterSecondFlush = changeSet.getLastCommittedVersion();
|
||||
assertNotEquals(lastCommittedVersionAfterSecondFlush, lastCommittedVersionAfterFirstFlush);
|
||||
|
||||
assertFalse(changeSet.isUnknownVersion(lastCommittedVersionAfterFirstFlush));
|
||||
assertFalse(changeSet.isUnknownVersion(lastCommittedVersionAfterSecondFlush));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inCaseOfFailure_inputVersionIsConsideredUnknown() throws IOException
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
|
||||
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
|
||||
|
||||
changeSet.addOrReplace("A1");
|
||||
changeSet.addOrReplace("A2");
|
||||
changeSet.delete("A3");
|
||||
changeSet.delete("A1");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersion = changeSet.getLastCommittedVersion();
|
||||
|
||||
// Force a NPE exception...
|
||||
changeSet.searcher = null;
|
||||
|
||||
// ...so a valid version is considered unknown even if it is valid
|
||||
assertTrue(changeSet.isUnknownVersion(lastCommittedVersion));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistentChangesetsAreMergedBeforeReturningToRequestor() throws IOException
|
||||
{
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
|
||||
|
||||
changeSet.addOrReplace("A1");
|
||||
changeSet.addOrReplace("A2");
|
||||
changeSet.delete("A3");
|
||||
changeSet.delete("A1");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
|
||||
assertNotEquals(SolrContentStore.NO_VERSION_AVAILABLE, lastCommittedVersionAfterFirstFlush);
|
||||
|
||||
changeSet.addOrReplace("A1");
|
||||
changeSet.addOrReplace("A3");
|
||||
changeSet.delete("A4");
|
||||
|
||||
changeSet.flush();
|
||||
|
||||
long lastCommittedVersionAfterSecondFlush = changeSet.getLastCommittedVersion();
|
||||
assertNotEquals(lastCommittedVersionAfterFirstFlush, lastCommittedVersionAfterSecondFlush);
|
||||
|
||||
ChangeSet changesSinceTheVeryBeginning = changeSet.since(SolrContentStore.NO_VERSION_AVAILABLE);
|
||||
|
||||
// ADDS = [A1, A2, A3]
|
||||
// DELS = [A4]
|
||||
assertEquals(3, changesSinceTheVeryBeginning.adds.size());
|
||||
assertEquals(1, changesSinceTheVeryBeginning.deletes.size());
|
||||
assertTrue(changesSinceTheVeryBeginning.adds.contains("A1"));
|
||||
assertTrue(changesSinceTheVeryBeginning.adds.contains("A2"));
|
||||
assertTrue(changesSinceTheVeryBeginning.adds.contains("A3"));
|
||||
assertTrue(changesSinceTheVeryBeginning.deletes.contains("A4"));
|
||||
|
||||
ChangeSet changesAfterSecondFlush = changeSet.since(lastCommittedVersionAfterFirstFlush);
|
||||
|
||||
// ADDS = [A1, A3]
|
||||
// DELS = [A4]
|
||||
assertEquals(2, changesAfterSecondFlush.adds.size());
|
||||
assertEquals(1, changesAfterSecondFlush.deletes.size());
|
||||
assertTrue(changesAfterSecondFlush.adds.contains("A1"));
|
||||
assertTrue(changesAfterSecondFlush.adds.contains("A3"));
|
||||
assertTrue(changesAfterSecondFlush.deletes.contains("A4"));
|
||||
}
|
||||
}
|
||||
+284
-156
@@ -18,47 +18,245 @@
|
||||
*/
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link SolrContentStoreTest}
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 5.0
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SolrContentStoreTest
|
||||
{
|
||||
private static final String DEFAULT_TENANT = "_DEFAULT_";
|
||||
private String solrHome = new File("./target/contentstoretest/").getAbsolutePath();
|
||||
private long dbid = 111;
|
||||
private String tenant = "me";
|
||||
|
||||
private String solrHome;
|
||||
private SolrContentStore contentStore;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
solrHome = new File("./target/contentstoretest/" + System.currentTimeMillis()).getAbsolutePath();
|
||||
contentStore = new SolrContentStore(solrHome);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException
|
||||
{
|
||||
contentStore.close();
|
||||
|
||||
File rootDir = new File(new SolrContentStore(solrHome).getRootLocation());
|
||||
FileUtils.deleteDirectory(rootDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atVeryBeginningAccessModeIsNotSet()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenAccessModeIsNotSetMethodCallsThrowsExceptionOrDoNothing()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
expectIllegalState(contentStore::getLastCommittedVersion);
|
||||
expectIllegalState(contentStore::setLastCommittedVersion, System.currentTimeMillis());
|
||||
expectIllegalState(contentStore::getChanges, System.currentTimeMillis());
|
||||
expectIllegalState(contentStore::removeDocFromContentStore, mock(NodeMetaData.class));
|
||||
expectIllegalState(contentStore::storeDocOnSolrContentStore, mock(NodeMetaData.class), mock(SolrInputDocument.class));
|
||||
|
||||
try
|
||||
{
|
||||
contentStore.flushChangeSet();
|
||||
fail();
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
fail();
|
||||
}
|
||||
catch(IllegalStateException expected)
|
||||
{
|
||||
// Nothing to be done here
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
contentStore.storeDocOnSolrContentStore(DEFAULT_TENANT, System.currentTimeMillis(), mock(SolrInputDocument.class));
|
||||
fail();
|
||||
}
|
||||
catch(IllegalStateException expected)
|
||||
{
|
||||
// Nothing to be done here
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastCommittedVersionInReadOnlyModeNotFound()
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastCommittedVersionInReadOnlyModeNotFoundBecauseException() throws IOException
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
Files.write(new File(contentStore.getRootLocation(), ".version").toPath(), "NAN".getBytes());
|
||||
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastCommittedVersionInReadOnlyModeNotFoundBecauseFileIsEmpty() throws IOException
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
File emptyVersionFile = new File(contentStore.getRootLocation(), ".version");
|
||||
emptyVersionFile.createNewFile();
|
||||
|
||||
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLastCommittedVersionInReadOnlyMode() throws IOException
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
long expectedLastCommittedVersion = System.currentTimeMillis();
|
||||
|
||||
Files.write(new File(contentStore.getRootLocation(), ".version").toPath(), Long.toString(expectedLastCommittedVersion).getBytes());
|
||||
|
||||
assertEquals(expectedLastCommittedVersion, contentStore.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLastCommittedVersionInReadOnlyMode()
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
long expectedLastCommittedVersion = System.currentTimeMillis();
|
||||
contentStore.setLastCommittedVersion(expectedLastCommittedVersion);
|
||||
|
||||
File versionFile = new File(contentStore.getRootLocation(), ".version");
|
||||
assertTrue(versionFile.canRead());
|
||||
|
||||
assertEquals(expectedLastCommittedVersion, contentStore.getLastCommittedVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangesInReadOnlyModeReturnsAnEmptyMap()
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
assertEquals(Collections.<String, List<Map<String, Object>>>emptyMap(), contentStore.getChanges(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromNotSetToReadOnlyMode()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromNotSetToReadWriteMode()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromReadOnlyToReadWriteMode()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromReadOnlyToReadOnlyHasNoEffect()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromReadWriteToReadOnlyModeHasNoEffect()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(true);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionFromReadWriteToReadWriteHasNoEffect()
|
||||
{
|
||||
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void contentStoreCreation_solrHomeNull_shouldThrowException()
|
||||
{
|
||||
@@ -117,180 +315,110 @@ public class SolrContentStoreTest
|
||||
Assert.assertThat(solrContentStore.getRootLocation(), is(solrHome + "/" + SolrContentStore.CONTENT_STORE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a content context using a string. The URL part will be the same if the
|
||||
* data provided is the same.
|
||||
*/
|
||||
private ContentContext createContentContext(String data)
|
||||
{
|
||||
return SolrContentUrlBuilder.start().add("data", data).getContentContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rootLocation()
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
File rootDir = new File(store.getRootLocation());
|
||||
Assert.assertTrue(rootDir.exists());
|
||||
Assert.assertTrue(rootDir.isDirectory());
|
||||
File rootDir = new File(contentStore.getRootLocation());
|
||||
|
||||
assertTrue(rootDir.exists());
|
||||
assertTrue(rootDir.isDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWriter()
|
||||
|
||||
@Test
|
||||
public void storeDocOnSolrContentStore()
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
|
||||
ContentContext ctx = createContentContext("abc");
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
String url = writer.getContentUrl();
|
||||
SolrInputDocument doc = mock(SolrInputDocument.class);
|
||||
long dbid = 111;
|
||||
String tenant = "me";
|
||||
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(tenant, dbid);
|
||||
Assert.assertNull(document);
|
||||
|
||||
Assert.assertNotNull(url);
|
||||
Assert.assertEquals("URL of the context does not match the writer URL. ", ctx.getContentUrl(), url);
|
||||
contentStore.storeDocOnSolrContentStore(tenant, dbid, doc);
|
||||
document = contentStore.retrieveDocFromSolrContentStore(tenant, dbid);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentByString()
|
||||
@Test
|
||||
public void storeDocOnSolrContentStoreNodeMetaData()
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
SolrInputDocument doc = mock(SolrInputDocument.class);
|
||||
NodeMetaData nodeMetaData = mock(NodeMetaData.class);
|
||||
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNull(document);
|
||||
|
||||
ContentContext ctx = createContentContext("abc");
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
contentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
|
||||
document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
File file = new File(store.getRootLocation() + "/" + writer.getContentUrl().replace("solr://", ""));
|
||||
Assert.assertFalse("File was created before anything was written", file.exists());
|
||||
@Test
|
||||
public void removeDocFromContentStore()
|
||||
{
|
||||
contentStore.toggleReadOnlyMode(false);
|
||||
SolrInputDocument doc = mock(SolrInputDocument.class);
|
||||
NodeMetaData nodeMetaData = mock(NodeMetaData.class);
|
||||
contentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
|
||||
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNotNull(document);
|
||||
|
||||
String content = "Quick brown fox jumps over the lazy dog.";
|
||||
writer.putContent(content);
|
||||
Assert.assertTrue("File was not created.", file.exists());
|
||||
contentStore.removeDocFromContentStore(nodeMetaData);
|
||||
document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNull(document);
|
||||
}
|
||||
|
||||
private void expectIllegalState(Supplier<?> function)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.putContent("Should not work");
|
||||
function.get();
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
catch (IllegalStateException expected)
|
||||
{
|
||||
// Expected
|
||||
// Nothing to do, this is expected
|
||||
}
|
||||
|
||||
// Now get the reader
|
||||
ContentReader reader = store.getReader(ctx.getContentUrl());
|
||||
Assert.assertNotNull(reader);
|
||||
Assert.assertTrue(reader.exists());
|
||||
|
||||
Assert.assertEquals(content, reader.getContentString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentByStream() throws Exception
|
||||
private <T> void expectIllegalState(Consumer<T> function, T arg)
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
|
||||
ContentContext ctx = createContentContext("abc");
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
|
||||
byte[] bytes = new byte[] { 1, 7, 13 };
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
writer.putContent(bis);
|
||||
|
||||
// Now get the reader
|
||||
ContentReader reader = store.getReader(ctx.getContentUrl());
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(3);
|
||||
reader.getContent(bos);
|
||||
Assert.assertEquals(bytes[0], bos.toByteArray()[0]);
|
||||
Assert.assertEquals(bytes[1], bos.toByteArray()[1]);
|
||||
Assert.assertEquals(bytes[2], bos.toByteArray()[2]);
|
||||
try
|
||||
{
|
||||
function.accept(arg);
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException expected)
|
||||
{
|
||||
// Nothing to do, this is expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() throws Exception
|
||||
private <I,O> void expectIllegalState(Function<I,O> function, I arg)
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
|
||||
ContentContext ctx = createContentContext("abc");
|
||||
String url = ctx.getContentUrl();
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
writer.putContent("Content goes here.");
|
||||
|
||||
// Check the reader
|
||||
ContentReader reader = store.getReader(url);
|
||||
Assert.assertNotNull(reader);
|
||||
Assert.assertTrue(reader.exists());
|
||||
|
||||
// Delete
|
||||
store.delete(url);
|
||||
reader = store.getReader(url);
|
||||
Assert.assertNotNull(reader);
|
||||
Assert.assertFalse(reader.exists());
|
||||
|
||||
// Delete when already gone; should just not fail
|
||||
store.delete(url);
|
||||
try
|
||||
{
|
||||
function.apply(arg);
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException expected)
|
||||
{
|
||||
// Nothing to do, this is expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A demonstration of how the store might be used.
|
||||
*/
|
||||
@Test
|
||||
public void exampleUsage()
|
||||
private <A, B> void expectIllegalState(BiConsumer<A, B> function, A arg1, B arg2)
|
||||
{
|
||||
SolrContentStore store = new SolrContentStore(solrHome);
|
||||
|
||||
String tenant = "alfresco.com";
|
||||
long dbId = 12345;
|
||||
String otherData = "sdfklsfdl";
|
||||
|
||||
ContentContext ctxWrite = SolrContentUrlBuilder.start()
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)).add(SolrContentUrlBuilder.KEY_TENANT, tenant)
|
||||
.add("otherData", otherData).getContentContext();
|
||||
ContentWriter writer = store.getWriter(ctxWrite);
|
||||
writer.putContent("a document in plain text");
|
||||
|
||||
// The URL can be reliably rebuilt in any order
|
||||
String urlRead = SolrContentUrlBuilder.start().add("otherData", otherData)
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, tenant).add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
|
||||
.get();
|
||||
ContentReader reader = store.getReader(urlRead);
|
||||
String documentText = reader.getContentString();
|
||||
|
||||
Assert.assertEquals("a document in plain text", documentText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storeDocOnSolrContentStore() throws IOException
|
||||
{
|
||||
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
|
||||
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
|
||||
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(tenant, dbid);
|
||||
Assert.assertNull(document);
|
||||
solrContentStore.storeDocOnSolrContentStore(tenant, dbid, doc);
|
||||
document = solrContentStore.retrieveDocFromSolrContentStore(tenant, dbid);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storeDocOnSolrContentStoreNodeMetaData() throws IOException
|
||||
{
|
||||
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
|
||||
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
|
||||
NodeMetaData nodeMetaData = Mockito.mock(NodeMetaData.class);
|
||||
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNull(document);
|
||||
solrContentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
|
||||
document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeDocFromContentStore() throws IOException
|
||||
{
|
||||
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
|
||||
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
|
||||
NodeMetaData nodeMetaData = Mockito.mock(NodeMetaData.class);
|
||||
solrContentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
|
||||
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNotNull(document);
|
||||
solrContentStore.removeDocFromContentStore(nodeMetaData);
|
||||
document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
|
||||
Assert.assertNull(document);
|
||||
try
|
||||
{
|
||||
function.accept(arg1, arg2);
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException expected)
|
||||
{
|
||||
// Nothing to do, this is expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -21,7 +21,7 @@ package org.alfresco.solr.content;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Tests {@link SolrContentUrlBuilder}
|
||||
@@ -85,12 +85,12 @@ public class SolrContentUrlBuilderTest
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, "12345")
|
||||
.get();
|
||||
Assert.assertEquals(url1, url1Check);
|
||||
|
||||
String url2 = SolrContentUrlBuilder
|
||||
.start()
|
||||
.add(SolrContentUrlBuilder.KEY_DB_ID, "123456")
|
||||
.get();
|
||||
Assert.assertNotEquals(url1, url2);
|
||||
|
||||
Assert.assertEquals("Incorrect URL: " + url1, "solr://default/db/1234/5.gz", url1);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ public class SolrContentUrlBuilderTest
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
|
||||
.get();
|
||||
Assert.assertEquals(url1, url1Check);
|
||||
|
||||
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/db/1234/56.gz", url1);
|
||||
}
|
||||
|
||||
@@ -126,7 +125,6 @@ public class SolrContentUrlBuilderTest
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
|
||||
.get();
|
||||
Assert.assertEquals(url1, url1Check);
|
||||
|
||||
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/acl/1234/56.gz", url1);
|
||||
}
|
||||
|
||||
@@ -146,7 +144,6 @@ public class SolrContentUrlBuilderTest
|
||||
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
|
||||
.get();
|
||||
Assert.assertEquals(url1, url1Check);
|
||||
|
||||
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/db/5432/1.gz", url1);
|
||||
}
|
||||
|
||||
@@ -164,6 +161,7 @@ public class SolrContentUrlBuilderTest
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SolrContentUrlBuilder
|
||||
@@ -176,6 +174,7 @@ public class SolrContentUrlBuilderTest
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SolrContentUrlBuilder
|
||||
@@ -188,6 +187,7 @@ public class SolrContentUrlBuilderTest
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SolrContentUrlBuilder
|
||||
@@ -200,6 +200,7 @@ public class SolrContentUrlBuilderTest
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SolrContentUrlBuilder
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package org.alfresco.solr.content;
|
||||
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SolrContentWriterTest
|
||||
{
|
||||
private String solrHome = new File("./target/contentwriter/").getAbsolutePath();
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException
|
||||
{
|
||||
File rootDir = new File(solrHome);
|
||||
FileUtils.deleteDirectory(rootDir);
|
||||
}
|
||||
|
||||
private ContentWriter getContentWriter(String name)
|
||||
{
|
||||
return new SolrFileContentWriter(new File(solrHome, name), solrHome + "/" + name);
|
||||
}
|
||||
|
||||
private ContentReader getContentReader(String name)
|
||||
{
|
||||
return new SolrFileContentReader(new File(solrHome, name), solrHome + "/" + name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentByString()
|
||||
{
|
||||
String filename = "abc";
|
||||
ContentWriter writer = getContentWriter(filename);
|
||||
|
||||
File file = new File(solrHome, filename);
|
||||
Assert.assertFalse("File was created before anything was written", file.exists());
|
||||
|
||||
String content = "Quick brown fox jumps over the lazy dog.";
|
||||
writer.putContent(content);
|
||||
Assert.assertTrue("File was not created.", file.exists());
|
||||
|
||||
try
|
||||
{
|
||||
writer.putContent("Should not work");
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Now get the reader
|
||||
ContentReader reader = getContentReader(filename);
|
||||
Assert.assertNotNull(reader);
|
||||
Assert.assertTrue(reader.exists());
|
||||
|
||||
Assert.assertEquals(content, reader.getContentString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentByStream()
|
||||
{
|
||||
|
||||
String filename = "cbs";
|
||||
ContentWriter writer = getContentWriter(filename);
|
||||
|
||||
byte[] bytes = new byte[] { 1, 7, 13 };
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
writer.putContent(bis);
|
||||
|
||||
// Now get the reader
|
||||
ContentReader reader = getContentReader(filename);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(3);
|
||||
reader.getContent(bos);
|
||||
Assert.assertEquals(bytes[0], bos.toByteArray()[0]);
|
||||
Assert.assertEquals(bytes[1], bos.toByteArray()[1]);
|
||||
Assert.assertEquals(bytes[2], bos.toByteArray()[2]);
|
||||
}
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.handler;
|
||||
|
||||
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
|
||||
import org.alfresco.solr.client.Acl;
|
||||
import org.alfresco.solr.client.AclChangeSet;
|
||||
import org.alfresco.solr.client.AclReaders;
|
||||
import org.alfresco.solr.client.Node;
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.alfresco.solr.client.SOLRAPIQueueClient;
|
||||
import org.alfresco.solr.client.Transaction;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.hadoop.util.Time;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAcl;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNode;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNodeMetaData;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
|
||||
import static org.alfresco.solr.utils.AlfrescoFileUtils.areDirectoryEquals;
|
||||
import static org.carrot2.shaded.guava.common.collect.ImmutableList.of;
|
||||
|
||||
/**
|
||||
* @author Elia Porciani
|
||||
*
|
||||
* This test check if the synchronization of contentstore between master and slave is done correctly.
|
||||
*/
|
||||
@SolrTestCaseJ4.SuppressSSL
|
||||
public class ContentStoreReplicationIT extends AbstractAlfrescoDistributedIT
|
||||
{
|
||||
|
||||
protected static JettySolrRunner master;
|
||||
protected static JettySolrRunner slave;
|
||||
protected static SolrClient masterClient;
|
||||
protected static SolrClient slaveClient;
|
||||
|
||||
protected static Path masterSolrHome;
|
||||
protected static Path slaveSolrHome;
|
||||
|
||||
protected static Path masterContentStore;
|
||||
protected static Path slaveContentStore;
|
||||
|
||||
private static Acl acl;
|
||||
private static final int MILLIS_TIMOUT = 80000;
|
||||
|
||||
@BeforeClass
|
||||
public static void createMasterSlaveEnv() throws Exception
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
|
||||
clientShards = new ArrayList<>();
|
||||
solrShards = new ArrayList<>();
|
||||
solrCollectionNameToStandaloneClient = new HashMap<>();
|
||||
jettyContainers = new HashMap<>();
|
||||
|
||||
String coreName = "master";
|
||||
boolean basicAuth = Boolean.parseBoolean(properties.getProperty("BasicAuth", "false"));
|
||||
|
||||
String masterDir = "master" + Time.now();
|
||||
String slaveDir = "slave" + Time.now();
|
||||
|
||||
String masterKey = masterDir + "/solrHome";
|
||||
String slaveKey = slaveDir + "/solrHome";
|
||||
|
||||
master = createJetty(masterKey, basicAuth);
|
||||
addCoreToJetty(masterKey, coreName, coreName, null);
|
||||
startJetty(master);
|
||||
|
||||
|
||||
String slaveCoreName = "slave";
|
||||
slave = createJetty(slaveKey, basicAuth);
|
||||
addCoreToJetty(slaveKey, slaveCoreName, slaveCoreName, null);
|
||||
setMasterUrl(slaveKey, slaveCoreName, master.getBaseUrl().toString() + "/master");
|
||||
|
||||
startJetty(slave);
|
||||
|
||||
String masterStr = buildUrl(master.getLocalPort()) + "/" + coreName;
|
||||
String slaveStr = buildUrl(slave.getLocalPort()) + "/" + slaveCoreName;
|
||||
|
||||
masterClient = createNewSolrClient(masterStr);
|
||||
slaveClient = createNewSolrClient(slaveStr);
|
||||
|
||||
masterSolrHome = testDir.toPath().resolve(masterKey);
|
||||
slaveSolrHome = testDir.toPath().resolve(slaveKey);
|
||||
masterContentStore = testDir.toPath().resolve(masterDir + "/contentstore");
|
||||
slaveContentStore = testDir.toPath().resolve(slaveDir + "/contentstore");
|
||||
|
||||
AclChangeSet aclChangeSet = getAclChangeSet(1);
|
||||
|
||||
acl = getAcl(aclChangeSet);
|
||||
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, singletonList("joel"), singletonList("phil"), null);
|
||||
|
||||
indexAclChangeSet(aclChangeSet,
|
||||
of(acl),
|
||||
of(aclReaders));
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void cleanupMasterSlave() throws Exception
|
||||
{
|
||||
master.stop();
|
||||
slave.stop();
|
||||
FileUtils.deleteQuietly(new File(masterSolrHome.getParent().toUri()));
|
||||
FileUtils.deleteQuietly(new File(slaveSolrHome.getParent().toUri()));
|
||||
|
||||
SOLRAPIQueueClient.nodeMetaDataMap.clear();
|
||||
SOLRAPIQueueClient.transactionQueue.clear();
|
||||
SOLRAPIQueueClient.aclChangeSetQueue.clear();
|
||||
SOLRAPIQueueClient.aclReadersMap.clear();
|
||||
SOLRAPIQueueClient.aclMap.clear();
|
||||
SOLRAPIQueueClient.nodeMap.clear();
|
||||
SOLRAPIQueueClient.nodeContentMap.clear();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void contentStoreReplicationTest() throws Exception
|
||||
{
|
||||
// ADD 250 nodes and check they are replicated
|
||||
int numNodes = 250;
|
||||
Transaction bigTxn = getTransaction(0, numNodes);
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
List<NodeMetaData> nodeMetaDatas = new ArrayList<>();
|
||||
for(int i = 0; i<numNodes; i++) {
|
||||
Node node = getNode(i, bigTxn, acl, Node.SolrApiNodeStatus.UPDATED);
|
||||
nodes.add(node);
|
||||
NodeMetaData nodeMetaData = getNodeMetaData(node, bigTxn, acl, "mike", null, false);
|
||||
node.setNodeRef(nodeMetaData.getNodeRef().toString());
|
||||
nodeMetaDatas.add(nodeMetaData);
|
||||
}
|
||||
|
||||
indexTransaction(bigTxn, nodes, nodeMetaDatas);
|
||||
|
||||
waitForDocCountCore(masterClient,
|
||||
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
|
||||
numNodes, MILLIS_TIMOUT, System.currentTimeMillis());
|
||||
|
||||
long filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
|
||||
.filter(Files::isRegularFile)
|
||||
.count();
|
||||
|
||||
Assert.assertEquals( "master contentStore should have " + numNodes + "files", numNodes, filesInMasterContentStore);
|
||||
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
|
||||
|
||||
|
||||
// ADD other 10 nodes
|
||||
int numUpdates = 10;
|
||||
int totalNodes = numNodes + numUpdates;
|
||||
Transaction updateTx = getTransaction(0, numUpdates);
|
||||
|
||||
List<Node> updateNodes = new ArrayList<>();
|
||||
List<NodeMetaData> updateNodeMetaDatas = new ArrayList<>();
|
||||
for(int i = numNodes; i < totalNodes; i++) {
|
||||
Node node = getNode(i, updateTx, acl, Node.SolrApiNodeStatus.UPDATED);
|
||||
updateNodes.add(node);
|
||||
NodeMetaData nodeMetaData = getNodeMetaData(node, updateTx, acl, "mike", null, false);
|
||||
node.setNodeRef(nodeMetaData.getNodeRef().toString());
|
||||
updateNodeMetaDatas.add(nodeMetaData);
|
||||
}
|
||||
|
||||
indexTransaction(updateTx, updateNodes, updateNodeMetaDatas);
|
||||
|
||||
waitForDocCountCore(masterClient,
|
||||
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
|
||||
numNodes + numUpdates, MILLIS_TIMOUT, System.currentTimeMillis());
|
||||
|
||||
filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
|
||||
.filter(Files::isRegularFile)
|
||||
.count();
|
||||
|
||||
Assert.assertEquals( "master contentStore should have " + totalNodes + "files", totalNodes, filesInMasterContentStore);
|
||||
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
|
||||
|
||||
|
||||
// DELETES 30 nodes
|
||||
int numDeletes = 30;
|
||||
Transaction deleteTx = getTransaction(numDeletes, 0);
|
||||
|
||||
totalNodes = numNodes + numUpdates - numDeletes;
|
||||
List<Node> deleteNodes = new ArrayList<>();
|
||||
List<NodeMetaData> deleteNodeMetaDatas = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i<numDeletes; i++) {
|
||||
Node node = getNode(i, deleteTx, acl, Node.SolrApiNodeStatus.DELETED);
|
||||
deleteNodes.add(node);
|
||||
NodeMetaData nodeMetaData = getNodeMetaData(node, deleteTx, acl, "mike", null, false);
|
||||
node.setNodeRef(nodeMetaData.getNodeRef().toString());
|
||||
deleteNodeMetaDatas.add(nodeMetaData);
|
||||
}
|
||||
|
||||
|
||||
indexTransaction(deleteTx, deleteNodes, deleteNodeMetaDatas);
|
||||
|
||||
waitForDocCountCore(masterClient,
|
||||
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
|
||||
totalNodes, MILLIS_TIMOUT, System.currentTimeMillis());
|
||||
|
||||
filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
|
||||
.filter(Files::isRegularFile)
|
||||
.count();
|
||||
|
||||
Assert.assertEquals( "master contentStore should have " + totalNodes + "files", totalNodes, filesInMasterContentStore);
|
||||
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static boolean waitForContentStoreSync(long waitMillis) throws InterruptedException
|
||||
{
|
||||
|
||||
long startMillis = System.currentTimeMillis();
|
||||
long timeout = startMillis + waitMillis;
|
||||
long increment = 1;
|
||||
|
||||
while(new Date().getTime() < timeout)
|
||||
{
|
||||
try{
|
||||
|
||||
if (areDirectoryEquals(masterContentStore, slaveContentStore, new String[]{"gz"}, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e){
|
||||
// do nothing
|
||||
}
|
||||
Thread.sleep(500 * increment);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void setMasterUrl(String jettyKey, String coreName, String masterUrl) throws IOException
|
||||
{
|
||||
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
|
||||
Path coreHome = jettySolrHome.resolve(coreName);
|
||||
Path confDir = coreHome.resolve("conf");
|
||||
|
||||
Path solrConfigPath = confDir.resolve("solrconfig.xml");
|
||||
|
||||
String content = new String(Files.readAllBytes(solrConfigPath));
|
||||
content = content.replaceAll("\\{masterURL\\}", masterUrl);
|
||||
Files.write(solrConfigPath, content.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class AlfrescoHighligherDistributedIT extends AbstractAlfrescoDistributed
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.query;
|
||||
|
||||
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.common.SolrDocumentList;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
/**
|
||||
* https://issues.alfresco.com/jira/browse/SEARCH-2012
|
||||
*/
|
||||
@SolrTestCaseJ4.SuppressSSL
|
||||
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
|
||||
public class AlfrescoSolrSortIT extends AbstractAlfrescoDistributedIT
|
||||
{
|
||||
@BeforeClass
|
||||
private static void initData() throws Throwable
|
||||
{
|
||||
initSolrServers(1, getClassName(), null);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearData() throws Exception
|
||||
{
|
||||
deleteByQueryAllClients("*:*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AlfrescoCollatableFieldType_emptyValuesSortingAsc__shouldBeRankedFirst() throws Exception {
|
||||
prepareIndexSegmentWithAllNonNullFieldValues("text@s__sort@{http://www.alfresco.org/model/content/1.0}title");
|
||||
putHandleDefaults();
|
||||
// Docs with id 1, 3, 5 and 6 should be first (note that these will be sorted by indexing time).
|
||||
String[] expectedRanking = new String[]{"1","3","5","6","4","2"};
|
||||
|
||||
QueryResponse response = query(getDefaultTestClient(), true,
|
||||
"{\"query\":\"(id:(1 2 3 4 5 6))\",\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
|
||||
params("qt", "/afts", "shards.qt", "/afts", "start", "0", "rows", "100", "sort", "text@s__sort@{http://www.alfresco.org/model/content/1.0}title asc"));
|
||||
|
||||
NamedList res = response.getResponse();
|
||||
SolrDocumentList searchResults = (SolrDocumentList)res.get("response");
|
||||
for(int i=0;i<searchResults.size();i++){
|
||||
assertThat(searchResults.get(i).get("id"),is(expectedRanking[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AlfrescoMLCollatableFieldType_emptyValuesSortingDesc_shouldRankThemLast() throws Exception {
|
||||
prepareIndexSegmentWithAllNonNullFieldValues("mltext@m__sort@{http://www.alfresco.org/model/content/1.0}title");
|
||||
|
||||
putHandleDefaults();
|
||||
// Docs with id 1, 3, 5 and 6 should be last (note that these will be sorted by indexing time).
|
||||
String[] expectedRanking = new String[]{"2","4","1","3","5","6"};
|
||||
|
||||
QueryResponse response = query(getDefaultTestClient(), true,
|
||||
"{\"query\":\"(id:(1 2 3 4 5 6))\",\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
|
||||
params("qt", "/afts", "shards.qt", "/afts", "start", "0", "rows", "100", "sort", "mltext@m__sort@{http://www.alfresco.org/model/content/1.0}title desc"));
|
||||
|
||||
NamedList res = response.getResponse();
|
||||
SolrDocumentList searchResults = (SolrDocumentList)res.get("response");
|
||||
for(int i=0;i<searchResults.size();i++){
|
||||
assertThat(searchResults.get(i).get("id"),is(expectedRanking[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/** Create six documents where the field is null for docs with id 1, 3, 5 and 6, and populated for docs with id 2 and 4. */
|
||||
private void prepareIndexSegmentWithAllNonNullFieldValues(String field) throws Exception {
|
||||
index(getDefaultTestClient(), true, "id", "1", "_version_", "0", field, "");
|
||||
index(getDefaultTestClient(), true, "id", "2", "_version_", "0", field, "B");
|
||||
index(getDefaultTestClient(), true, "id", "3", "_version_", "0", field, "");
|
||||
index(getDefaultTestClient(), true, "id", "4", "_version_", "0", field, "A");
|
||||
index(getDefaultTestClient(), true, "id", "5", "_version_", "0", field, "");
|
||||
index(getDefaultTestClient(), true, "id", "6", "_version_", "0", field, "");
|
||||
commit(getDefaultTestClient(), true);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ import static org.hamcrest.core.Is.is;
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class DistributedAlfrescoSolrSpellcheckerIT extends AbstractAlfrescoDistr
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.alfresco.solr.IndexTrackingShutdownException;
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.AclChangeSet;
|
||||
import org.alfresco.solr.client.AclChangeSets;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.commons.lang.reflect.FieldUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link AclTracker} class.
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AclTrackerTest
|
||||
{
|
||||
private static final Object CUSTOM_ALFRESCO_VERSION = "99.9.9";
|
||||
// The class under test
|
||||
private AclTracker tracker;
|
||||
private @Mock SOLRAPIClient client;
|
||||
private @Mock InformationServer informationServer;
|
||||
private TrackerState trackerState;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
trackerState = new TrackerState();
|
||||
trackerState.setRunning(false); // Nothing would happen if it were already running.
|
||||
Properties props = createProperties();
|
||||
// Spy in order to stub some methods and verify others. Note: non-stubbed methods go to the real class.
|
||||
tracker = spy(new AclTracker(props, client, "core-name", informationServer));
|
||||
}
|
||||
|
||||
private Properties createProperties()
|
||||
{
|
||||
Properties props = new Properties();
|
||||
props.put("alfresco.stores", "workspace://SpacesStore");
|
||||
props.put("alfresco.version", CUSTOM_ALFRESCO_VERSION);
|
||||
return props;
|
||||
}
|
||||
|
||||
protected void testTrackChangesRan()
|
||||
{
|
||||
when(tracker.getTrackerState()).thenReturn(trackerState);
|
||||
|
||||
tracker.track();
|
||||
|
||||
assertFalse(trackerState.isRunning());
|
||||
assertFalse(trackerState.isCheck());
|
||||
}
|
||||
|
||||
|
||||
// High level test of doTrack() workflow.
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void checkTrackingOperaionsPerformed() throws Throwable
|
||||
{
|
||||
testTrackChangesRan();
|
||||
|
||||
verify(tracker).purgeAclChangeSets();
|
||||
verify(tracker).purgeAcls();
|
||||
|
||||
verify(tracker).reindexAclChangeSets();
|
||||
verify(tracker).reindexAcls();
|
||||
|
||||
verify(tracker).indexAclChangeSets();
|
||||
verify(tracker).indexAcls();
|
||||
|
||||
verify(tracker).trackRepository();
|
||||
}
|
||||
|
||||
|
||||
// Tests the purgeAclChangeSets() call made in AclTracker.doTrack()
|
||||
// TODO: the other operations in doTrack().
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void checkTrackingWhenAclChangeSetsToPurge() throws IllegalAccessException, IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
ConcurrentLinkedQueue<Long> aclChangeSetsToPurge = (ConcurrentLinkedQueue<Long>)
|
||||
FieldUtils.readField(tracker, "aclChangeSetsToPurge", true);
|
||||
aclChangeSetsToPurge.add(101L);
|
||||
aclChangeSetsToPurge.add(102L);
|
||||
aclChangeSetsToPurge.add(103L);
|
||||
|
||||
// Invoke the behaviour we're testing
|
||||
testTrackChangesRan();
|
||||
|
||||
// aclChangeSetsToPurge
|
||||
verify(informationServer).deleteByAclChangeSetId(101L);
|
||||
verify(informationServer).deleteByAclChangeSetId(102L);
|
||||
verify(informationServer).deleteByAclChangeSetId(103L);
|
||||
|
||||
// TODO: verify checkShutdown
|
||||
verify(informationServer).commit();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void checkTrackingWhenAclsToPurge() throws IllegalAccessException, IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
ConcurrentLinkedQueue<Long> aclsToPurge = (ConcurrentLinkedQueue<Long>)
|
||||
FieldUtils.readField(tracker, "aclsToPurge", true);
|
||||
aclsToPurge.add(201L);
|
||||
aclsToPurge.add(202L);
|
||||
aclsToPurge.add(203L);
|
||||
|
||||
// Invoke the behaviour we're testing
|
||||
testTrackChangesRan();
|
||||
|
||||
// aclsToPurge
|
||||
verify(informationServer).deleteByAclId(201L);
|
||||
verify(informationServer).deleteByAclId(202L);
|
||||
verify(informationServer).deleteByAclId(203L);
|
||||
|
||||
// TODO: verify checkShutdown
|
||||
verify(informationServer).commit();
|
||||
}
|
||||
|
||||
// TODO: Commented out, approach not working
|
||||
// Rethink or finish later.
|
||||
/*@Test
|
||||
public void checkTrackingWhenAclChangeSetsToReindex() throws IllegalAccessException, IOException, AuthenticationException, JSONException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
ConcurrentLinkedQueue<Long> aclChangeSetsToReindex = (ConcurrentLinkedQueue<Long>)
|
||||
FieldUtils.readField(tracker, "aclChangeSetsToReindex", true);
|
||||
aclChangeSetsToReindex.add(301L);
|
||||
aclChangeSetsToReindex.add(302L);
|
||||
aclChangeSetsToReindex.add(303L);
|
||||
|
||||
// tracker will loop through list of ACLs to reindex, and get changesets for each changeset ID.
|
||||
when(client.getAclChangeSets(null, 201L, null, 202L, 1)).thenReturn(mockChangeSets(201L));
|
||||
when(client.getAclChangeSets(null, 202L, null, 203L, 1)).thenReturn(mockChangeSets(202L));
|
||||
when(client.getAclChangeSets(null, 203L, null, 204L, 1)).thenReturn(mockChangeSets(203L));
|
||||
|
||||
when(client.getAcls(Collections.singletonList(new AclChangeSet(201L, 0L, 1)), null, Integer.MAX_VALUE)).thenReturn(Collections.singletonList(new Acl(201L, 0L)));
|
||||
when(client.getAcls(Collections.singletonList(new AclChangeSet(202L, 0L, 1)), null, Integer.MAX_VALUE)).thenReturn(Collections.singletonList(new Acl(202L, 0L)));
|
||||
when(client.getAcls(Collections.singletonList(new AclChangeSet(203L, 0L, 1)), null, Integer.MAX_VALUE)).thenReturn(Collections.singletonList(new Acl(203L, 0L)));
|
||||
|
||||
// TODO: ...more...
|
||||
|
||||
// Invoke the behaviour we're testing
|
||||
testTrackChangesRan();
|
||||
|
||||
verify(informationServer).deleteByAclChangeSetId(301L);
|
||||
verify(informationServer).deleteByAclChangeSetId(302L);
|
||||
verify(informationServer).deleteByAclChangeSetId(303L);
|
||||
|
||||
// TODO: verify checkShutdown
|
||||
verify(informationServer).commit();
|
||||
}*/
|
||||
|
||||
private AclChangeSets mockChangeSets(long id)
|
||||
{
|
||||
List<AclChangeSet> changeSets = Collections.singletonList(new AclChangeSet(id, 0L, 1));
|
||||
AclChangeSets acs = mock(AclChangeSets.class);
|
||||
when(acs.getAclChangeSets()).thenReturn(changeSets);
|
||||
return acs;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void trackingAbortsWhenAlreadyRunning() throws Throwable
|
||||
{
|
||||
trackerState.setRunning(true);
|
||||
// Prove running state, before attempt to track()
|
||||
assertTrue(trackerState.isRunning());
|
||||
|
||||
FieldUtils.writeField(tracker, "state", trackerState, true);
|
||||
tracker.track();
|
||||
|
||||
// Still running - these values are unaffected.
|
||||
assertTrue(trackerState.isRunning());
|
||||
assertFalse(trackerState.isCheck());
|
||||
|
||||
// Prove doTrack() was not called
|
||||
verify(tracker, never()).doTrack();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void willRollbackOnThrowableDuringTracking() throws Throwable
|
||||
{
|
||||
doThrow(new RuntimeException("Simulated problem during tracking")).when(tracker).doTrack();
|
||||
|
||||
testTrackChangesRan();
|
||||
|
||||
verify(informationServer).rollback();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void willRollbackOnIndexTrackingShutdownException() throws Throwable
|
||||
{
|
||||
doThrow(new IndexTrackingShutdownException()).when(tracker).doTrack();
|
||||
|
||||
testTrackChangesRan();
|
||||
|
||||
verify(informationServer).rollback();
|
||||
}
|
||||
|
||||
@Ignore("Not yet implemented.")
|
||||
@Test
|
||||
public void canCheckIndex()
|
||||
{
|
||||
// TODO
|
||||
// tracker.checkIndex(fromTx, toTx, fromAclTx, toAclTx, fromTime, toTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void canGetAlfrescoVersion()
|
||||
{
|
||||
// Check we're testing something useful
|
||||
assertNotNull(CUSTOM_ALFRESCO_VERSION);
|
||||
// Check alfresco version retrieved from properties
|
||||
assertEquals(CUSTOM_ALFRESCO_VERSION, tracker.getAlfrescoVersion());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore("Superseded by AlfrescoSolrTrackerTest")
|
||||
public void canClose() throws IllegalAccessException
|
||||
{
|
||||
ThreadHandler threadHandler = (ThreadHandler) FieldUtils.readField(tracker, "threadHandler", true);
|
||||
threadHandler = spy(threadHandler);
|
||||
FieldUtils.writeField(tracker, "threadHandler", threadHandler, true);
|
||||
|
||||
tracker.shutdown();
|
||||
|
||||
// AclTracker specific
|
||||
verify(threadHandler).shutDownThreadPool();
|
||||
|
||||
// Applicable to all AbstractAclTracker
|
||||
verify(client).close();
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2019 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
|
||||
import org.alfresco.solr.client.Acl;
|
||||
import org.alfresco.solr.client.AclChangeSet;
|
||||
import org.alfresco.solr.client.AclReaders;
|
||||
import org.alfresco.solr.client.ContentPropertyValue;
|
||||
import org.alfresco.solr.client.Node;
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.alfresco.solr.client.StringPropertyValue;
|
||||
import org.alfresco.solr.client.Transaction;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.MAX_WAIT_TIME;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAcl;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNode;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNodeMetaData;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.list;
|
||||
|
||||
/**
|
||||
* @author Elia Porciani
|
||||
*/
|
||||
@SolrTestCaseJ4.SuppressSSL
|
||||
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
|
||||
public class ContentPropertyValueTrackerIT extends AbstractAlfrescoDistributedIT
|
||||
{
|
||||
|
||||
final private String authorField = "text@s__lt@{http://www.alfresco.org/model/content/1.0}author";
|
||||
|
||||
@BeforeClass
|
||||
private static void initData() throws Throwable
|
||||
{
|
||||
initSolrServers(1, "ContentPropertyValueTrackerIT", null);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* MNT-21076 (SEARCH-1906)
|
||||
*
|
||||
* This test aims to test that a document with more than one (not indexed) ContentPropertyValue
|
||||
* is not removed from the index after any update. The fix prevents an NPE during an update because the
|
||||
* not indexed ContentPropertyValue is searched and not found in the cached document.
|
||||
*
|
||||
* The second ContentPropertyValue must not be indexed.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testDataWithMultipleContentValueIsIndexedAfterUpdateTest() throws Exception
|
||||
{
|
||||
putHandleDefaults();
|
||||
AclChangeSet aclChangeSet = getAclChangeSet(1, 1);
|
||||
Acl acl = getAcl(aclChangeSet);
|
||||
|
||||
// Arbitrary acl data.
|
||||
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, list("joel"), list("phil"), null);
|
||||
indexAclChangeSet(aclChangeSet,
|
||||
list(acl),
|
||||
list(aclReaders));
|
||||
|
||||
Transaction txn = getTransaction(0, 1);
|
||||
Node fileNode = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
|
||||
NodeMetaData fileMetaData = getNodeMetaData(fileNode, txn, acl, "mike", null, false);
|
||||
String author = "Mario";
|
||||
|
||||
// Here we set the PROP_TITLE as a ContentPropertyValue.
|
||||
// The value is not indexed because the related indexedField is not found (it does not exist).
|
||||
fileMetaData.getProperties()
|
||||
.put(ContentModel.PROP_TITLE, new ContentPropertyValue(Locale.CANADA, 100, "UTF8", "txt", 10l));
|
||||
fileMetaData.getProperties().put(ContentModel.PROP_AUTHOR, new StringPropertyValue(author));
|
||||
indexTransaction(txn,
|
||||
list(fileNode),
|
||||
list(fileMetaData));
|
||||
|
||||
// Check the document is correctly indexed
|
||||
waitForDocCount(new TermQuery(new Term(authorField, author)), 1, MAX_WAIT_TIME);
|
||||
|
||||
// Update the author.
|
||||
Transaction txn1 = getTransaction(0, 1);
|
||||
String authorAfterUpdate = "Luigi";
|
||||
fileMetaData.getProperties().put(ContentModel.PROP_AUTHOR, new StringPropertyValue(authorAfterUpdate));
|
||||
indexTransaction(txn1,
|
||||
list(fileNode),
|
||||
list(fileMetaData));
|
||||
|
||||
// Check the document is still indexed after the update.
|
||||
waitForDocCount(new TermQuery(new Term(authorField, authorAfterUpdate)), 1, MAX_WAIT_TIME);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class DistributedAclIdAlfrescoSolrTrackerIT extends AbstractAlfrescoDistr
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public class DistributedAlfrescoSolrJsonIT extends AbstractAlfrescoDistributedIT
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ public class DistributedAlfrescoSolrTrackerIT extends AbstractAlfrescoDistribute
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public class DistributedAlfrescoSolrTrackerRaceIT extends AbstractAlfrescoDistri
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ public class DistributedDateQuarterAlfrescoSolrTrackerIT extends DistributedDate
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class DistributedDateSplitYearAlfrescoSolrTrackerIT extends DistributedDa
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class DistributedDbidRangeAlfrescoSolrTrackerIT extends AbstractAlfrescoD
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class DistributedExpandDbidRangeAlfrescoSolrTrackerIT extends AbstractAlf
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ public class DistributedExplicitShardIdWithStaticPropertyRouterIT extends Abstra
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ public class DistributedExplicitShardRoutingTrackerIT extends AbstractAlfrescoDi
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public class DistributedPropertyBasedAlfrescoSolrTrackerIT extends AbstractAlfre
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
-1
@@ -78,7 +78,6 @@ public class SolrTrackerSchedulerTest
|
||||
props.put("shard.method", "SHARD_METHOD_DBID");
|
||||
props.put("alfresco.template", "");
|
||||
props.put("alfresco.index.transformContent", "true");
|
||||
props.put("alfresco.version", "5.0.0");
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class CachedDocTransformerIT extends AbstractAlfrescoDistributedIT
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
private static void destroyData() throws Throwable
|
||||
private static void destroyData()
|
||||
{
|
||||
dismissSolrServers();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
The folder contains several Solr configuration files that are used within the test suite.
|
||||
Note that the folder, although it has the structure that Solr expects (<core name>/conf), it is not a complete "core"
|
||||
folder, because
|
||||
|
||||
- some files are missing (e.g. schema.xml)
|
||||
- some files are used only in specific tests (e.g. solrconfig-rerank.xml, schema-rerank.xml)
|
||||
|
||||
During the build process, Maven creates a complete core definition under the test build output folder by merging the
|
||||
configuration of the "Rerank" template (src/main/resources/templates/rerank) together with the content of this folder.
|
||||
|
||||
Note that this folder is copied **after** the template, so duplicates will be overwritten. For example, both folders
|
||||
have a *solrcore.properties* and a *solrconfig.xml*: the test execution will use are those in this folder (because they
|
||||
will overwrite the same files in the rerank template).
|
||||
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This is the Solr schema file. This file should be named "schema.xml" and
|
||||
should be in the conf directory under the solr home
|
||||
(i.e. ./solr/conf/schema.xml by default)
|
||||
or located where the classloader for the Solr webapp can find it.
|
||||
|
||||
This example schema is the recommended starting point for users.
|
||||
It should be kept correct and concise, usable out-of-the-box.
|
||||
|
||||
For more information, on how to customize this file, please see
|
||||
http://wiki.apache.org/solr/SchemaXml
|
||||
-->
|
||||
|
||||
<schema name="example" version="1.1">
|
||||
<!-- attribute "name" is the name of this schema and is only used for display purposes.
|
||||
Applications should change this to reflect the nature of the search collection.
|
||||
version="1.1" is Solr's version number for the schema syntax and semantics. It should
|
||||
not normally be changed by applications.
|
||||
1.0: multiValued attribute did not exist, all fields are multiValued by nature
|
||||
1.1: multiValued attribute introduced, false by default -->
|
||||
|
||||
|
||||
<!-- field type definitions. The "name" attribute is
|
||||
just a label to be used by field definitions. The "class"
|
||||
attribute and any other attributes determine the real
|
||||
behavior of the fieldType.
|
||||
Class names starting with "solr" refer to java classes in the
|
||||
org.apache.solr.analysis package.
|
||||
-->
|
||||
|
||||
<!-- The StrField type is not analyzed, but indexed/stored verbatim.
|
||||
- StrField and TextField support an optional compressThreshold which
|
||||
limits compression (if enabled in the derived fields) to values which
|
||||
exceed a certain size (in characters).
|
||||
-->
|
||||
<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
<!-- boolean type: "true" or "false" -->
|
||||
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
<!-- The optional sortMissingLast and sortMissingFirst attributes are
|
||||
currently supported on types that are sorted internally as strings.
|
||||
- If sortMissingLast="true", then a sort on this field will cause documents
|
||||
without the field to come after documents with the field,
|
||||
regardless of the requested sort order (asc or desc).
|
||||
- If sortMissingFirst="true", then a sort on this field will cause documents
|
||||
without the field to come before documents with the field,
|
||||
regardless of the requested sort order.
|
||||
- If sortMissingLast="false" and sortMissingFirst="false" (the default),
|
||||
then default lucene sorting will be used which places docs without the
|
||||
field first in an ascending sort and last in a descending sort.
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Default numeric field types. For faster range queries, consider the tint/tfloat/tlong/tdouble types.
|
||||
-->
|
||||
<fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
|
||||
|
||||
|
||||
<!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
|
||||
is a more restricted form of the canonical representation of dateTime
|
||||
http://www.w3.org/TR/xmlschema-2/#dateTime
|
||||
The trailing "Z" designates UTC time and is mandatory.
|
||||
Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
|
||||
All other components are mandatory.
|
||||
|
||||
Expressions can also be used to denote calculations that should be
|
||||
performed relative to "NOW" to determine the value, ie...
|
||||
|
||||
NOW/HOUR
|
||||
... Round to the start of the current hour
|
||||
NOW-1DAY
|
||||
... Exactly 1 day prior to now
|
||||
NOW/DAY+6MONTHS+3DAYS
|
||||
... 6 months and 3 days in the future from the start of
|
||||
the current day
|
||||
|
||||
Consult the TrieDateField javadocs for more information.
|
||||
-->
|
||||
<fieldType name="date" class="solr.TrieDateField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
|
||||
<!-- The "RandomSortField" is not used to store or search any
|
||||
data. You can declare fields of this type it in your schema
|
||||
to generate psuedo-random orderings of your docs for sorting
|
||||
purposes. The ordering is generated based on the field name
|
||||
and the version of the index, As long as the index version
|
||||
remains unchanged, and the same field name is reused,
|
||||
the ordering of the docs will be consistent.
|
||||
If you want differend psuedo-random orderings of documents,
|
||||
for the same version of the index, use a dynamicField and
|
||||
change the name
|
||||
-->
|
||||
<fieldType name="random" class="solr.RandomSortField" indexed="true" />
|
||||
|
||||
<!-- solr.TextField allows the specification of custom text analyzers
|
||||
specified as a tokenizer and a list of token filters. Different
|
||||
analyzers may be specified for indexing and querying.
|
||||
|
||||
The optional positionIncrementGap puts space between multiple fields of
|
||||
this type on the same document, with the purpose of preventing false phrase
|
||||
matching across fields.
|
||||
|
||||
For more info on customizing your analyzer chain, please see
|
||||
http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
|
||||
-->
|
||||
|
||||
<!-- One can also specify an existing Analyzer class that has a
|
||||
default constructor via the class attribute on the analyzer element
|
||||
<fieldType name="text_greek" class="solr.TextField">
|
||||
<analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
|
||||
</fieldType>
|
||||
-->
|
||||
|
||||
<!-- A text field that only splits on whitespace for exact matching of words -->
|
||||
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- A text field that uses WordDelimiterFilter to enable splitting and matching of
|
||||
words on case-change, alpha numeric boundaries, and non-alphanumeric chars,
|
||||
so that a query of "wifi" or "wi fi" could match a document containing "Wi-Fi".
|
||||
Synonyms and stopwords are customized by external files, and stemming is enabled.
|
||||
Duplicate tokens at the same position (which may result from Stemmed Synonyms or
|
||||
WordDelim parts) are removed.
|
||||
-->
|
||||
<fieldType name="text" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer type="index">
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<!-- in this example, we will only use synonyms at query time
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
|
||||
-->
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.PorterStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
<analyzer type="query">
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.PorterStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
|
||||
<fieldType name="text_plain" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.ICUTokenizerFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- Less flexible matching, but less false matches. Probably not ideal for product names,
|
||||
but may be good for SKUs. Can insert dashes in the wrong place and still match. -->
|
||||
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
|
||||
<analyzer>
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.EnglishMinimalStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- This is an example of using the KeywordTokenizer along
|
||||
With various TokenFilterFactories to produce a sortable field
|
||||
that does not include some properties of the source text
|
||||
-->
|
||||
<fieldType name="alphaOnlySort" class="solr.TextField" sortMissingLast="true" omitNorms="true">
|
||||
<analyzer>
|
||||
<!-- KeywordTokenizer does no actual tokenizing, so the entire
|
||||
input string is preserved as a single token
|
||||
-->
|
||||
<tokenizer class="solr.MockTokenizerFactory" pattern="keyword"/>
|
||||
<!-- The LowerCase TokenFilter does what you expect, which can be
|
||||
when you want your sorting to be case insensitive
|
||||
-->
|
||||
<filter class="solr.LowerCaseFilterFactory" />
|
||||
<!-- The TrimFilter removes any leading or trailing whitespace -->
|
||||
<filter class="solr.TrimFilterFactory" />
|
||||
<!-- The PatternReplaceFilter gives you the flexibility to use
|
||||
Java Regular expression to replace any sequence of characters
|
||||
matching a pattern with an arbitrary replacement string,
|
||||
which may include back refrences to portions of the orriginal
|
||||
string matched by the pattern.
|
||||
|
||||
See the Java Regular Expression documentation for more
|
||||
infomation on pattern and replacement string syntax.
|
||||
|
||||
http://docs.oracle.com/javase/7/docs/api/java/util/regex/package-summary.html
|
||||
-->
|
||||
<filter class="solr.PatternReplaceFilterFactory"
|
||||
pattern="([^a-z])" replacement="" replace="all"
|
||||
/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- since fields of this type are by default not stored or indexed, any data added to
|
||||
them will be ignored outright
|
||||
-->
|
||||
<fieldType name="ignored" stored="false" indexed="false" class="solr.StrField" />
|
||||
|
||||
<fieldType name="file" keyField="id" defVal="1" stored="false" indexed="false" class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
<fieldType name="sfile" keyField="sfile_s" defVal="1" stored="false" indexed="false" class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
|
||||
<fieldType name="tint" class="solr.TrieIntField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tfloat" class="solr.TrieFloatField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tlong" class="solr.TrieLongField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdouble" class="solr.TrieDoubleField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdouble4" class="solr.TrieDoubleField" precisionStep="4" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" positionIncrementGap="0"/>
|
||||
|
||||
|
||||
<fieldType name="tints" class="solr.TrieIntField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
<fieldType name="tfloats" class="solr.TrieFloatField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true"/>
|
||||
<fieldType name="tlongs" class="solr.TrieLongField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true"/>
|
||||
<fieldType name="tdoubles" class="solr.TrieDoubleField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
<fieldType name="tdates" class="solr.TrieDateField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
|
||||
<!-- Poly field -->
|
||||
<fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/>
|
||||
<fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
|
||||
<fieldType name="geohash" class="solr.GeoHashField"/>
|
||||
|
||||
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
|
||||
|
||||
<!-- A specialized field for geospatial search. If indexed, this fieldType must not be multi
|
||||
valued. -->
|
||||
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
|
||||
|
||||
<!-- These should pass right through and insure that we can declare external field types -->
|
||||
<fieldType name="eff_float" keyField="id" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
<fieldType name="eff_tfloat" keyField="eff_ti" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField" valType="tfloat"/>
|
||||
|
||||
<!-- Be sure that the valType can be optional Since valType has done nothing up until now, this is preferred -->
|
||||
<fieldType name="eff_none" keyField="id" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField"/>
|
||||
|
||||
<fieldType name="text_no_analyzer" stored="false" indexed="true" class="solr.TextField" />
|
||||
|
||||
<fieldType name="text_length" class="solr.TextField">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.StandardTokenizerFactory"/>
|
||||
<filter class="solr.StandardFilterFactory"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.LengthFilterFactory" min="2" max="32768"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
|
||||
|
||||
<!-- Valid attributes for fields:
|
||||
name: mandatory - the name for the field
|
||||
type: mandatory - the name of a previously defined type from the <types> section
|
||||
indexed: true if this field should be indexed (searchable or sortable)
|
||||
stored: true if this field should be retrievable
|
||||
multiValued: true if this field may contain multiple values per document
|
||||
omitNorms: (expert) set to true to omit the norms associated with
|
||||
this field (this disables length normalization and index-time
|
||||
boosting for the field, and saves some memory). Only full-text
|
||||
fields or fields that need an index-time boost need norms.
|
||||
termVectors: [false] set to true to store the term vector for a given field.
|
||||
When using MoreLikeThis, fields used for similarity should be stored for
|
||||
best performance.
|
||||
-->
|
||||
|
||||
<!-- for testing, a type that does a transform to see if it's correctly done everywhere -->
|
||||
<field name="id" type="float" indexed="true" stored="true" required="true" />
|
||||
<field name="FINGERPRINT" type="text_plain" indexed="true" omitNorms="true" stored="false" multiValued="false" required="false" docValues="false"/>
|
||||
<field name="ACLID" type="long" indexed="true" stored="true" docValues="true" />
|
||||
<field name="READER" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="DENIED" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="DOC_TYPE" type="string" indexed="true" stored="true" docValues="true" />
|
||||
|
||||
<field name="text" type="text" indexed="true" stored="false" />
|
||||
|
||||
<!-- Test a point field for distances -->
|
||||
<field name="point" type="xy" indexed="true" stored="true" multiValued="false"/>
|
||||
<field name="pointD" type="xyd" indexed="true" stored="true" multiValued="false"/>
|
||||
|
||||
<field name="point_hash" type="geohash" indexed="true" stored="true" multiValued="false"/>
|
||||
|
||||
<field name="signatureField" type="string" indexed="true" stored="false"/>
|
||||
|
||||
<field name="eff_trie" type="eff_tfloat" />
|
||||
|
||||
<field name="text_no_analyzer" type="text_no_analyzer" indexed="true" />
|
||||
|
||||
<field name="_version_" type="long" indexed="true" stored="true" multiValued="false" />
|
||||
|
||||
<field name="cat" type="string" indexed="true" stored="true" multiValued="true"/>
|
||||
<field name="cat_docValues" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_intDocValues" type="int" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_floatDocValues" type="float" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_length" type="text_length" indexed="true" stored="true" multiValued="true"/>
|
||||
|
||||
|
||||
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
|
||||
will be used if the name matches any of the patterns.
|
||||
RESTRICTION: the glob-like pattern in the name attribute must have
|
||||
a "*" only at the start or the end.
|
||||
EXAMPLE: name="*_i" will match any field ending in _i (like myid_i, z_i)
|
||||
Longer patterns will be matched first. if equal size patterns
|
||||
both match, the first appearing in the schema will be used. -->
|
||||
<dynamicField name="*_s" type="string" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_s_dv" type="string" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_ss" type="string" indexed="true" stored="true" multiValued="true"/>
|
||||
<dynamicField name="*_sS" type="string" indexed="false" stored="true"/>
|
||||
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ii" type="int" indexed="true" stored="true" multiValued="true"/>
|
||||
<dynamicField name="*_l" type="long" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_f" type="float" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_d" type="double" indexed="true" stored="true"/>
|
||||
|
||||
<dynamicField name="*_ti" type="tint" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ti_dv" type="int" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_ti_ni_dv" type="int" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tl" type="tlong" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tl_dv" type="tlong" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tl_ni_dv" type="tlong" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tf" type="tfloat" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tf_dv" type="tfloat" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tf_ni_dv" type="tfloat" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_td" type="tdouble" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_td_dv" type="tdouble" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_td_ni_dv" type="tdouble" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdt" type="tdate" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tdt_dv" type="tdate" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdt_ni_dv" type="tdate" indexed="false" stored="true" docValues="true"/>
|
||||
|
||||
<dynamicField name="*_tis" type="tints" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tis_dv" type="tints" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tis_ni_dv" type="tints" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tls" type="tlongs" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tls_dv" type="tlongs" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tls_ni_dv" type="tlongs" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tfs" type="tfloats" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tfs_dv" type="tfloats" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tfs_ni_dv" type="tfloats" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tds" type="tdoubles" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tds_dv" type="tdoubles" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tds_ni_dv" type="tdoubles" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdts" type="tdates" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tdts_dv" type="tdates" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdts_ni_dv" type="tdates" indexed="false" stored="true" docValues="true"/>
|
||||
|
||||
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ws" type="text_ws" indexed="true" stored="true"/>
|
||||
|
||||
<dynamicField name="*_extf" type="file"/>
|
||||
<dynamicField name="*_extfs" type="sfile"/>
|
||||
|
||||
<dynamicField name="*_random" type="random" />
|
||||
|
||||
<!-- uncomment the following to ignore any fields that don't already match an existing
|
||||
field name or dynamic field, rather than reporting them as an error.
|
||||
alternately, change the type="ignored" to some other type e.g. "text" if you want
|
||||
unknown fields indexed and/or stored by default -->
|
||||
<!--dynamicField name="*" type="ignored" /-->
|
||||
|
||||
|
||||
<!-- Field to use to determine and enforce document uniqueness.
|
||||
Unless this field is marked with required="false", it will be a required field
|
||||
-->
|
||||
<uniqueKey>id</uniqueKey>
|
||||
|
||||
</schema>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!--
|
||||
|
||||
A solrconfig.xml snippet containing indexConfig settings for randomized testing.
|
||||
|
||||
-->
|
||||
<indexConfig>
|
||||
<!-- this sys property is not set by SolrTestCaseJ4 because we ideally want to use
|
||||
the RandomMergePolicy in all tests - but some tests expect very specific
|
||||
Merge behavior, so those tests can set it as needed.
|
||||
-->
|
||||
<mergePolicy class="${solr.tests.mergePolicy:org.apache.solr.util.RandomMergePolicy}" />
|
||||
|
||||
<useCompoundFile>${useCompoundFile:false}</useCompoundFile>
|
||||
|
||||
<maxBufferedDocs>${solr.tests.maxBufferedDocs}</maxBufferedDocs>
|
||||
<maxIndexingThreads>${solr.tests.maxIndexingThreads}</maxIndexingThreads>
|
||||
<ramBufferSizeMB>${solr.tests.ramBufferSizeMB}</ramBufferSizeMB>
|
||||
|
||||
<mergeScheduler class="${solr.tests.mergeScheduler}" />
|
||||
|
||||
<writeLockTimeout>1000</writeLockTimeout>
|
||||
<commitLockTimeout>10000</commitLockTimeout>
|
||||
|
||||
<!-- this sys property is not set by SolrTestCaseJ4 because almost all tests should
|
||||
use the single process lockType for speed - but tests that explicitly need
|
||||
to vary the lockType canset it as needed.
|
||||
-->
|
||||
<lockType>${solr.tests.lockType:single}</lockType>
|
||||
</indexConfig>
|
||||
+568
@@ -0,0 +1,568 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- This is a "kitchen sink" config file that tests can use.
|
||||
When writting a new test, feel free to add *new* items (plugins,
|
||||
config options, etc...) as long as they don't break any existing
|
||||
tests. if you need to test something esoteric please add a new
|
||||
"solrconfig-your-esoteric-purpose.xml" config file.
|
||||
|
||||
Note in particular that this test is used by MinimalSchemaTest so
|
||||
Anything added to this file needs to work correctly even if there
|
||||
is now uniqueKey or defaultSearch Field.
|
||||
-->
|
||||
|
||||
<config>
|
||||
|
||||
<jmx />
|
||||
|
||||
<!-- Used to specify an alternate directory to hold all index data.
|
||||
It defaults to "index" if not present, and should probably
|
||||
not be changed if replication is in use. -->
|
||||
<dataDir>${solr.data.dir:}</dataDir>
|
||||
|
||||
<!-- The DirectoryFactory to use for indexes.
|
||||
solr.StandardDirectoryFactory, the default, is filesystem based.
|
||||
solr.RAMDirectoryFactory is memory based and not persistent. -->
|
||||
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}">
|
||||
<double name="maxWriteMBPerSecDefault">1000000</double>
|
||||
<double name="maxWriteMBPerSecFlush">2000000</double>
|
||||
<double name="maxWriteMBPerSecMerge">3000000</double>
|
||||
<double name="maxWriteMBPerSecRead">4000000</double>
|
||||
</directoryFactory>
|
||||
|
||||
<schemaFactory class="ClassicIndexSchemaFactory"/>
|
||||
|
||||
<luceneMatchVersion>${tests.luceneMatchVersion:LUCENE_CURRENT}</luceneMatchVersion>
|
||||
|
||||
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
|
||||
<updateHandler class="solr.DirectUpdateHandler2">
|
||||
|
||||
<!-- autocommit pending docs if certain criteria are met
|
||||
<autoCommit>
|
||||
<maxDocs>10000</maxDocs>
|
||||
<maxTime>3600000</maxTime>
|
||||
</autoCommit>
|
||||
-->
|
||||
<!-- represents a lower bound on the frequency that commits may
|
||||
occur (in seconds). NOTE: not yet implemented
|
||||
|
||||
<commitIntervalLowerBound>0</commitIntervalLowerBound>
|
||||
-->
|
||||
|
||||
<!-- The RunExecutableListener executes an external command.
|
||||
exe - the name of the executable to run
|
||||
dir - dir to use as the current working directory. default="."
|
||||
wait - the calling thread waits until the executable returns. default="true"
|
||||
args - the arguments to pass to the program. default=nothing
|
||||
env - environment variables to set. default=nothing
|
||||
-->
|
||||
<!-- A postCommit event is fired after every commit
|
||||
<listener event="postCommit" class="solr.RunExecutableListener">
|
||||
<str name="exe">/var/opt/resin3/__PORT__/scripts/solr/snapshooter</str>
|
||||
<str name="dir">/var/opt/resin3/__PORT__</str>
|
||||
<bool name="wait">true</bool>
|
||||
<arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
|
||||
<arr name="env"> <str>MYVAR=val1</str> </arr>
|
||||
</listener>
|
||||
-->
|
||||
<!--
|
||||
<updateLog enable="${enable.update.log:true}">
|
||||
<str name="dir">${solr.ulog.dir:}</str>
|
||||
</updateLog>
|
||||
|
||||
<commitWithin>
|
||||
<softCommit>${solr.commitwithin.softcommit:true}</softCommit>
|
||||
</commitWithin>
|
||||
-->
|
||||
|
||||
</updateHandler>
|
||||
|
||||
<query>
|
||||
<!-- Maximum number of clauses in a boolean query... can affect
|
||||
range or wildcard queries that expand to big boolean
|
||||
queries. An exception is thrown if exceeded.
|
||||
-->
|
||||
<maxBooleanClauses>1024</maxBooleanClauses>
|
||||
|
||||
<!-- Cache specification for Filters or DocSets - unordered set of *all* documents
|
||||
that match a particular query.
|
||||
-->
|
||||
<filterCache
|
||||
class="solr.search.FastLRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="2"/>
|
||||
|
||||
<queryResultCache
|
||||
class="solr.search.LRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="2"/>
|
||||
|
||||
<documentCache
|
||||
class="solr.search.LRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="0"/>
|
||||
|
||||
<cache name="perSegFilter"
|
||||
class="solr.search.LRUCache"
|
||||
size="10"
|
||||
initialSize="0"
|
||||
autowarmCount="10" />
|
||||
|
||||
<!-- If true, stored fields that are not requested will be loaded lazily.
|
||||
-->
|
||||
<enableLazyFieldLoading>true</enableLazyFieldLoading>
|
||||
|
||||
<!--
|
||||
|
||||
<cache name="myUserCache"
|
||||
class="solr.search.LRUCache"
|
||||
size="4096"
|
||||
initialSize="1024"
|
||||
autowarmCount="1024"
|
||||
regenerator="MyRegenerator"
|
||||
/>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<useFilterForSortedQuery>true</useFilterForSortedQuery>
|
||||
-->
|
||||
|
||||
<queryResultWindowSize>10</queryResultWindowSize>
|
||||
|
||||
<!-- set maxSize artificially low to exercise both types of sets -->
|
||||
<HashDocSet maxSize="3" loadFactor="0.75"/>
|
||||
|
||||
<!-- boolToFilterOptimizer converts boolean clauses with zero boost
|
||||
into cached filters if the number of docs selected by the clause exceeds
|
||||
the threshold (represented as a fraction of the total index)
|
||||
-->
|
||||
<boolTofilterOptimizer enabled="false" cacheSize="32" threshold=".05"/>
|
||||
|
||||
<!-- a newSearcher event is fired whenever a new searcher is being prepared
|
||||
and there is a current searcher handling requests (aka registered). -->
|
||||
<!-- QuerySenderListener takes an array of NamedList and executes a
|
||||
local query request for each NamedList in sequence. -->
|
||||
<!--
|
||||
<listener event="newSearcher" class="solr.QuerySenderListener">
|
||||
<arr name="queries">
|
||||
<lst> <str name="q">solr</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
<lst> <str name="q">rocks</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
</arr>
|
||||
</listener>
|
||||
-->
|
||||
|
||||
<!-- a firstSearcher event is fired whenever a new searcher is being
|
||||
prepared but there is no current registered searcher to handle
|
||||
requests or to gain prewarming data from. -->
|
||||
<!--
|
||||
<listener event="firstSearcher" class="solr.QuerySenderListener">
|
||||
<arr name="queries">
|
||||
<lst> <str name="q">fast_warm</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
</arr>
|
||||
</listener>
|
||||
-->
|
||||
<listener event="firstSearcher" class="org.alfresco.solr.lifecycle.SolrCoreLoadListener" />
|
||||
</query>
|
||||
|
||||
<queryResponseWriter name="xml" default="true"
|
||||
class="solr.XMLResponseWriter" />
|
||||
|
||||
<requestHandler name="/replication" class="org.alfresco.solr.handler.AlfrescoReplicationHandler">
|
||||
<lst name="master">
|
||||
<str name="replicateAfter">commit</str>
|
||||
<str name="confFiles">schema.xml</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
|
||||
<!-- An alternate set representation that uses an integer hash to store filters (sets of docids).
|
||||
If the set cardinality <= maxSize elements, then HashDocSet will be used instead of the bitset
|
||||
based HashBitset. -->
|
||||
|
||||
<!-- requestHandler plugins... incoming queries will be dispatched to the
|
||||
correct handler based on the 'qt' param matching the
|
||||
name of registered handlers.
|
||||
The "standard" request handler is the default and will be used if qt
|
||||
is not specified in the request.
|
||||
-->
|
||||
<requestHandler name="standard" class="solr.StandardRequestHandler">
|
||||
<bool name="httpCaching">true</bool>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/get" class="solr.RealTimeGetHandler">
|
||||
<lst name="defaults">
|
||||
<str name="omitHeader">true</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="dismax" class="solr.SearchHandler" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">dismax</str>
|
||||
<str name="q.alt">*:*</str>
|
||||
<float name="tie">0.01</float>
|
||||
<str name="qf">
|
||||
text^0.5 features_t^1.0 subject^1.4 title_stemmed^2.0
|
||||
</str>
|
||||
<str name="pf">
|
||||
text^0.2 features_t^1.1 subject^1.4 title_stemmed^2.0 title^1.5
|
||||
</str>
|
||||
<str name="bf">
|
||||
ord(weight)^0.5 recip(rord(iind),1,1000,1000)^0.3
|
||||
</str>
|
||||
<str name="mm">
|
||||
3<-1 5<-2 6<90%
|
||||
</str>
|
||||
<int name="ps">100</int>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<!-- test query parameter defaults -->
|
||||
<requestHandler name="defaults" class="solr.StandardRequestHandler">
|
||||
<lst name="defaults">
|
||||
<int name="rows">4</int>
|
||||
<bool name="hl">true</bool>
|
||||
<str name="hl.fl">text,name,subject,title,whitetok</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<!-- test query parameter defaults -->
|
||||
<requestHandler name="lazy" class="solr.StandardRequestHandler" startup="lazy">
|
||||
<lst name="defaults">
|
||||
<int name="rows">4</int>
|
||||
<bool name="hl">true</bool>
|
||||
<str name="hl.fl">text,name,subject,title,whitetok</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/update" class="solr.UpdateRequestHandler" />
|
||||
|
||||
<searchComponent name="fingerprint" class="org.alfresco.solr.component.FingerPrintComponent"/>
|
||||
|
||||
<requestHandler name="/fingerprint" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<arr name="components">
|
||||
<str>fingerprint</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/afts" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">afts</str>
|
||||
<str name="spellcheck">false</str>
|
||||
<str name="spellcheck.extendedResults">false</str>
|
||||
<str name="spellcheck.count">5</str>
|
||||
<str name="spellcheck.alternativeTermCount">2</str>
|
||||
<str name="spellcheck.maxResultsForSuggest">5</str>
|
||||
<str name="spellcheck.collate">true</str>
|
||||
<str name="spellcheck.collateExtendedResults">true</str>
|
||||
<str name="spellcheck.maxCollationTries">5</str>
|
||||
<str name="spellcheck.maxCollations">3</str>
|
||||
<str name="carrot.title">mltext@m___t@{http://www.alfresco.org/model/content/1.0}title</str>
|
||||
<str name="carrot.url">id</str>
|
||||
<str name="carrot.snippet">content@s___t@{http://www.alfresco.org/model/content/1.0}content</str>
|
||||
<bool name="carrot.produceSummary">true</bool>
|
||||
<bool name="carrot.outputSubClusters">false</bool>
|
||||
</lst>
|
||||
<arr name="components">
|
||||
<str>setLocale</str>
|
||||
<str>rewriteFacetParameters</str>
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>facet_module</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
<str>clearLocale</str>
|
||||
<str>rewriteFacetCounts</str>
|
||||
<str>spellcheck</str>
|
||||
<str>spellcheckbackcompat</str>
|
||||
<str>setProcessedDenies</str>
|
||||
</arr>
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/native" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<!-- default values for query parameters can be specified, these
|
||||
will be overridden by parameters in the request
|
||||
-->
|
||||
<lst name="defaults">
|
||||
<str name="echoParams">explicit</str>
|
||||
<int name="rows">10</int>
|
||||
<str name="df">suggest</str>
|
||||
</lst>
|
||||
|
||||
|
||||
<arr name="components">
|
||||
<str>setLocale</str>
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
<str>clearLocale</str>
|
||||
</arr>
|
||||
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/cmis" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">cmis</str>
|
||||
</lst>
|
||||
<arr name="components">
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
</arr>
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent name="termsComp" class="org.apache.solr.handler.component.TermsComponent"/>
|
||||
|
||||
<requestHandler name="/terms" class="org.apache.solr.handler.component.SearchHandler">
|
||||
<arr name="components">
|
||||
<str>termsComp</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="mltrh" class="org.apache.solr.handler.component.SearchHandler">
|
||||
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent name="tvComponent" class="org.apache.solr.handler.component.TermVectorComponent"/>
|
||||
|
||||
<requestHandler name="tvrh" class="org.apache.solr.handler.component.SearchHandler">
|
||||
<lst name="defaults">
|
||||
|
||||
</lst>
|
||||
<arr name="last-components">
|
||||
<str>tvComponent</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/mlt" class="solr.MoreLikeThisHandler">
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent class="solr.HighlightComponent" name="highlight">
|
||||
<highlighting class="org.apache.solr.handler.component.AlfrescoSolrHighlighter">
|
||||
<!-- Configure the standard fragmenter -->
|
||||
<fragmenter name="gap" class="org.apache.solr.highlight.GapFragmenter" default="true">
|
||||
<lst name="defaults">
|
||||
<int name="hl.fragsize">100</int>
|
||||
</lst>
|
||||
</fragmenter>
|
||||
|
||||
<fragmenter name="regex" class="org.apache.solr.highlight.RegexFragmenter">
|
||||
<lst name="defaults">
|
||||
<!-- slightly smaller fragsizes work better because of slop -->
|
||||
<int name="hl.fragsize">70</int>
|
||||
<!-- allow 50% slop on fragment sizes -->
|
||||
<float name="hl.regex.slop">0.5</float>
|
||||
<!-- a basic sentence pattern -->
|
||||
<str name="hl.regex.pattern">[-\w ,/\n\"']{20,200}</str>
|
||||
</lst>
|
||||
</fragmenter>
|
||||
|
||||
<!-- Configure the standard formatter -->
|
||||
<formatter name="html" class="org.apache.solr.highlight.HtmlFormatter" default="true">
|
||||
<lst name="defaults">
|
||||
<str name="hl.simple.pre"><![CDATA[<em>]]></str>
|
||||
<str name="hl.simple.post"><![CDATA[</em>]]></str>
|
||||
</lst>
|
||||
</formatter>
|
||||
|
||||
<!-- Configure the standard encoder -->
|
||||
<encoder name="html" class="solr.highlight.HtmlEncoder"/>
|
||||
|
||||
<!-- Configure the standard fragListBuilder -->
|
||||
<fragListBuilder name="simple" class="solr.highlight.SimpleFragListBuilder"/>
|
||||
|
||||
<!-- Configure the single fragListBuilder -->
|
||||
<fragListBuilder name="single" class="solr.highlight.SingleFragListBuilder"/>
|
||||
|
||||
<!-- Configure the weighted fragListBuilder -->
|
||||
<fragListBuilder name="weighted" default="true" class="solr.highlight.WeightedFragListBuilder"/>
|
||||
|
||||
<!-- default tag FragmentsBuilder -->
|
||||
<fragmentsBuilder name="default" default="true" class="solr.highlight.ScoreOrderFragmentsBuilder">
|
||||
<!--
|
||||
<lst name="defaults">
|
||||
<str name="hl.multiValuedSeparatorChar">/</str>
|
||||
</lst>
|
||||
-->
|
||||
</fragmentsBuilder>
|
||||
|
||||
<!-- multi-colored tag FragmentsBuilder -->
|
||||
<fragmentsBuilder name="colored" class="solr.highlight.ScoreOrderFragmentsBuilder">
|
||||
<lst name="defaults">
|
||||
<str name="hl.tag.pre"><![CDATA[
|
||||
<b style="background:yellow">,<b style="background:lawgreen">,
|
||||
<b style="background:aquamarine">,<b style="background:magenta">,
|
||||
<b style="background:palegreen">,<b style="background:coral">,
|
||||
<b style="background:wheat">,<b style="background:khaki">,
|
||||
<b style="background:lime">,<b style="background:deepskyblue">]]></str>
|
||||
<str name="hl.tag.post"><![CDATA[</b>]]></str>
|
||||
</lst>
|
||||
</fragmentsBuilder>
|
||||
|
||||
<boundaryScanner name="default" default="true" class="solr.highlight.SimpleBoundaryScanner">
|
||||
<lst name="defaults">
|
||||
<str name="hl.bs.maxScan">10</str>
|
||||
<str name="hl.bs.chars">.,!? 	 </str>
|
||||
</lst>
|
||||
</boundaryScanner>
|
||||
|
||||
<boundaryScanner name="breakIterator" class="solr.highlight.BreakIteratorBoundaryScanner">
|
||||
<lst name="defaults">
|
||||
<!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
|
||||
<str name="hl.bs.type">WORD</str>
|
||||
<!-- language and country are used when constructing Locale object. -->
|
||||
<!-- And the Locale object will be used when getting instance of BreakIterator -->
|
||||
<str name="hl.bs.language">en</str>
|
||||
<str name="hl.bs.country">US</str>
|
||||
</lst>
|
||||
</boundaryScanner>
|
||||
</highlighting>
|
||||
</searchComponent>
|
||||
|
||||
<!-- enable streaming for testing... -->
|
||||
<requestDispatcher handleSelect="true" >
|
||||
<requestParsers enableRemoteStreaming="true" multipartUploadLimitInKB="2048" />
|
||||
<httpCaching lastModifiedFrom="openTime" etagSeed="Solr" never304="false">
|
||||
<cacheControl>max-age=30, public</cacheControl>
|
||||
</httpCaching>
|
||||
</requestDispatcher>
|
||||
|
||||
<!-- Echo the request contents back to the client -->
|
||||
<requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
|
||||
<lst name="defaults">
|
||||
<str name="echoParams">explicit</str>
|
||||
<str name="echoHandler">true</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<admin>
|
||||
<defaultQuery>solr</defaultQuery>
|
||||
<gettableFiles>solrconfig.xml schema.xml admin-extra.html</gettableFiles>
|
||||
</admin>
|
||||
|
||||
<!-- test getting system property -->
|
||||
<!--
|
||||
<propTest attr1="${solr.test.sys.prop1}-$${literal}"
|
||||
attr2="${non.existent.sys.prop:default-from-config}">prefix-${solr.test.sys.prop2}-suffix</propTest>
|
||||
-->
|
||||
|
||||
<queryParser name="alfrescoReRank" class="org.alfresco.solr.query.AlfrescoReRankQParserPlugin"/>
|
||||
<queryParser name="afts" class="org.alfresco.solr.query.AlfrescoFTSQParserPlugin"/>
|
||||
|
||||
<!--
|
||||
<queryParser name="afts" class="org.alfresco.solr.query.AlfrescoFTSQParserPlugin">
|
||||
<str name="rerankPhase">SINGLE_PASS</str>
|
||||
</queryParser>
|
||||
-->
|
||||
|
||||
<queryParser name="cmis" class="org.alfresco.solr.query.CmisQParserPlugin"/>
|
||||
|
||||
<xi:include href="solrconfig_insight.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:fallback/>
|
||||
</xi:include>
|
||||
|
||||
<queryParser name="mimetype" class="org.alfresco.solr.query.MimetypeGroupingQParserPlugin" >
|
||||
<str name="mapping">conf/mime_types.csv</str>
|
||||
</queryParser>
|
||||
|
||||
<queryParser name="contentSize" class="org.alfresco.solr.query.ContentSizeGroupingQParserPlugin" >
|
||||
<int name="scale">1</int>
|
||||
<int name="buckets">10</int>
|
||||
</queryParser>
|
||||
<searchComponent name="setLocale" class="org.alfresco.solr.component.SetLocaleComponent" />
|
||||
<searchComponent name="clearLocale" class="org.alfresco.solr.component.ClearLocaleComponent" />
|
||||
|
||||
<!-- Spell Check
|
||||
|
||||
The spell check component can return a list of alternative spelling
|
||||
suggestions.
|
||||
|
||||
http://wiki.apache.org/solr/SpellCheckComponent
|
||||
-->
|
||||
<searchComponent name="spellcheck" class="org.alfresco.solr.component.spellcheck.AlfrescoSpellCheckComponent">
|
||||
|
||||
<str name="queryAnalyzerFieldType">text_shingle</str>
|
||||
|
||||
<!-- Multiple "Spell Checkers" can be declared and used by this
|
||||
component
|
||||
-->
|
||||
|
||||
<!-- a spellchecker built from a field of the main index -->
|
||||
<lst name="spellchecker">
|
||||
<str name="name">default</str>
|
||||
<str name="field">suggest</str>
|
||||
<str name="classname">solr.DirectSolrSpellChecker</str>
|
||||
<!-- the spellcheck distance measure used, the default is the internal levenshtein -->
|
||||
<str name="distanceMeasure">internal</str>
|
||||
<!-- minimum accuracy needed to be considered a valid spellcheck suggestion -->
|
||||
<float name="accuracy">0.5</float>
|
||||
<!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 -->
|
||||
<int name="maxEdits">2</int>
|
||||
<!-- the minimum shared prefix when enumerating terms -->
|
||||
<int name="minPrefix">1</int>
|
||||
<!-- maximum number of inspections per result. -->
|
||||
<int name="maxInspections">5</int>
|
||||
<!-- minimum length of a query term to be considered for correction -->
|
||||
<int name="minQueryLength">4</int>
|
||||
<!-- maximum threshold of documents a query term can appear to be considered for correction -->
|
||||
<float name="maxQueryFrequency">0.01</float>
|
||||
<!-- uncomment this to require suggestions to occur in 1% of the documents
|
||||
<float name="thresholdTokenFrequency">.01</float>
|
||||
-->
|
||||
</lst>
|
||||
|
||||
<!-- word break -->
|
||||
<lst name="spellchecker">
|
||||
<str name="name">wordbreak</str>
|
||||
<str name="field">suggest</str>
|
||||
<str name="classname">solr.WordBreakSolrSpellChecker</str>
|
||||
<str name="combineWords">true</str>
|
||||
<str name="breakWords">true</str>
|
||||
<int name="maxChanges">10</int>
|
||||
<int name="minBreakLength">5</int>
|
||||
</lst>
|
||||
|
||||
</searchComponent>
|
||||
<searchComponent name="spellcheckbackcompat" class="org.alfresco.solr.component.spellcheck.AlfrescoSpellCheckBackCompatComponent"/>
|
||||
<searchComponent name="rewriteFacetParameters" class="org.alfresco.solr.component.RewriteFacetParametersComponent" />
|
||||
<searchComponent name="rewriteFacetCounts" class="org.alfresco.solr.component.RewriteFacetCountsComponent" />
|
||||
<searchComponent name="setProcessedDenies" class="org.alfresco.solr.component.SetProcessedDeniesComponent" />
|
||||
|
||||
<transformer name="cached" class="org.alfresco.solr.transformer.CachedDocTransformerFactory" >
|
||||
</transformer>
|
||||
|
||||
|
||||
|
||||
</config>
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
#
|
||||
# solrcore.properties - used in solrconfig.xml
|
||||
#
|
||||
|
||||
enable.alfresco.tracking=true
|
||||
|
||||
#
|
||||
#These are replaced by the admin handler
|
||||
#
|
||||
#data.dir.root=DATA_DIR
|
||||
#data.dir.store=workspace/SpacesStore
|
||||
#alfresco.stores=workspace://SpacesStore
|
||||
|
||||
#
|
||||
# Properties loaded during alfresco tracking
|
||||
#
|
||||
|
||||
alfresco.host=localhost
|
||||
alfresco.port=8080
|
||||
alfresco.port.ssl=8443
|
||||
alfresco.baseUrl=/alfresco
|
||||
alfresco.cron=0/2 * * * * ? *
|
||||
|
||||
#alfresco.index.transformContent=false
|
||||
#alfresco.ignore.datatype.1=d:content
|
||||
alfresco.lag=1000
|
||||
alfresco.hole.retention=3600000
|
||||
# alfresco.hole.check.after is not used yet
|
||||
# It will reduce the hole checking load
|
||||
alfresco.hole.check.after=300000
|
||||
alfresco.batch.count=1000
|
||||
|
||||
# encryption
|
||||
|
||||
# none, https
|
||||
alfresco.secureComms=none
|
||||
|
||||
# ssl
|
||||
alfresco.encryption.ssl.keystore.type=JCEKS
|
||||
alfresco.encryption.ssl.keystore.provider=
|
||||
alfresco.encryption.ssl.keystore.location=ssl.repo.client.keystore
|
||||
alfresco.encryption.ssl.keystore.passwordFileLocation=ssl-keystore-passwords.properties
|
||||
alfresco.encryption.ssl.truststore.type=JCEKS
|
||||
alfresco.encryption.ssl.truststore.provider=
|
||||
alfresco.encryption.ssl.truststore.location=ssl.repo.client.truststore
|
||||
alfresco.encryption.ssl.truststore.passwordFileLocation=ssl-truststore-passwords.properties
|
||||
|
||||
# Tracking
|
||||
|
||||
alfresco.corePoolSize=1
|
||||
alfresco.maximumPoolSize=-1
|
||||
alfresco.keepAliveTime=120
|
||||
alfresco.threadPriority=5
|
||||
alfresco.threadDaemon=true
|
||||
alfresco.workQueueSize=-1
|
||||
|
||||
# HTTP Client
|
||||
|
||||
alfresco.maxTotalConnections=200
|
||||
alfresco.maxHostConnections=200
|
||||
alfresco.socketTimeout=360000
|
||||
|
||||
# SOLR caching
|
||||
|
||||
solr.filterCache.size=256
|
||||
solr.filterCache.initialSize=128
|
||||
solr.queryResultCache.size=1024
|
||||
solr.queryResultCache.initialSize=1024
|
||||
solr.documentCache.size=1024
|
||||
solr.documentCache.initialSize=1024
|
||||
solr.queryResultMaxDocsCached=2048
|
||||
|
||||
solr.authorityCache.size=128
|
||||
solr.authorityCache.initialSize=64
|
||||
solr.pathCache.size=256
|
||||
solr.pathCache.initialSize=128
|
||||
|
||||
solr.ownerCache.size=128
|
||||
solr.ownerCache.initialSize=64
|
||||
|
||||
solr.readerCache.size=128
|
||||
solr.readerCache.initialSize=64
|
||||
|
||||
solr.deniedCache.size=128
|
||||
solr.deniedCache.initialSize=64
|
||||
|
||||
# SOLR
|
||||
|
||||
solr.maxBooleanClauses=10000
|
||||
|
||||
# Batch fetch
|
||||
|
||||
alfresco.transactionDocsBatchSize=100
|
||||
alfresco.nodeBatchSize=10
|
||||
alfresco.changeSetAclsBatchSize=100
|
||||
alfresco.aclBatchSize=10
|
||||
alfresco.contentReadBatchSize=4000
|
||||
alfresco.contentUpdateBatchSize=1000
|
||||
|
||||
# Warming
|
||||
|
||||
solr.filterCache.autowarmCount=32
|
||||
solr.authorityCache.autowarmCount=4
|
||||
solr.pathCache.autowarmCount=32
|
||||
solr.deniedCache.autowarmCount=0
|
||||
solr.readerCache.autowarmCount=0
|
||||
solr.ownerCache.autowarmCount=0
|
||||
solr.queryResultCache.autowarmCount=4
|
||||
solr.documentCache.autowarmCount=512
|
||||
|
||||
solr.queryResultWindowSize=512
|
||||
|
||||
|
||||
#
|
||||
# TODO
|
||||
#
|
||||
# cross language support
|
||||
# locale expansion
|
||||
# logging check report ....
|
||||
#
|
||||
#
|
||||
|
||||
alfresco.commitInterval=1000
|
||||
alfresco.newSearcherInterval=2000
|
||||
|
||||
alfresco.doPermissionChecks=true
|
||||
|
||||
|
||||
#
|
||||
# Metadata pulling control
|
||||
#
|
||||
alfresco.metadata.skipDescendantDocsForSpecificTypes=false
|
||||
alfresco.metadata.ignore.datatype.0=cm:person
|
||||
alfresco.metadata.ignore.datatype.1=app:configurations
|
||||
alfresco.metadata.skipDescendantDocsForSpecificAspects=false
|
||||
#alfresco.metadata.ignore.aspect.0=
|
||||
|
||||
|
||||
#
|
||||
# Suggestions
|
||||
#
|
||||
solr.suggester.enabled=false
|
||||
# -1 to disable suggester build throttling
|
||||
solr.suggester.minSecsBetweenBuilds=3600
|
||||
|
||||
#
|
||||
# Limit the maximum text size of transformed content sent to the index - in bytes
|
||||
#
|
||||
alfresco.contentStreamLimit=10000000
|
||||
|
||||
#Sharding default values
|
||||
shard.instance=1
|
||||
shard.count=0
|
||||
shard.method=DB_ID
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
a
|
||||
an
|
||||
and
|
||||
are
|
||||
as
|
||||
at
|
||||
be
|
||||
but
|
||||
by
|
||||
for
|
||||
if
|
||||
in
|
||||
into
|
||||
is
|
||||
it
|
||||
no
|
||||
not
|
||||
of
|
||||
on
|
||||
or
|
||||
s
|
||||
such
|
||||
t
|
||||
that
|
||||
the
|
||||
their
|
||||
then
|
||||
there
|
||||
these
|
||||
they
|
||||
this
|
||||
to
|
||||
was
|
||||
will
|
||||
with
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#some test synonym mappings unlikely to appear in real input text
|
||||
aaa => aaaa
|
||||
bbb => bbbb1 bbbb2
|
||||
ccc => cccc1,cccc2
|
||||
a\=>a => b\=>b
|
||||
a\,a => b\,b
|
||||
fooaaa,baraaa,bazaaa
|
||||
|
||||
# Some synonym groups specific to this example
|
||||
GB,gib,gigabyte,gigabytes
|
||||
MB,mib,megabyte,megabytes
|
||||
Television, Televisions, TV, TVs
|
||||
#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming
|
||||
#after us won't split it into two words.
|
||||
|
||||
# Synonym mappings can be used for spelling correction too
|
||||
pixima => pixma
|
||||
|
||||
# Test synonyms
|
||||
quick,fast,rapid,speedy
|
||||
brown fox jumped,leaping reynard,springer
|
||||
lazy,bone idle
|
||||
@@ -0,0 +1,14 @@
|
||||
The folder contains several Solr configuration files that are used within the test suite.
|
||||
Note that the folder, although it has the structure that Solr expects (<core name>/conf), it is not a complete "core"
|
||||
folder, because
|
||||
|
||||
- some files are missing (e.g. schema.xml)
|
||||
- some files are used only in specific tests (e.g. solrconfig-rerank.xml, schema-rerank.xml)
|
||||
|
||||
During the build process, Maven creates a complete core definition under the test build output folder by merging the
|
||||
configuration of the "Rerank" template (src/main/resources/templates/rerank) together with the content of this folder.
|
||||
|
||||
Note that this folder is copied **after** the template, so duplicates will be overwritten. For example, both folders
|
||||
have a *solrcore.properties* and a *solrconfig.xml*: the test execution will use are those in this folder (because they
|
||||
will overwrite the same files in the rerank template).
|
||||
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This is the Solr schema file. This file should be named "schema.xml" and
|
||||
should be in the conf directory under the solr home
|
||||
(i.e. ./solr/conf/schema.xml by default)
|
||||
or located where the classloader for the Solr webapp can find it.
|
||||
|
||||
This example schema is the recommended starting point for users.
|
||||
It should be kept correct and concise, usable out-of-the-box.
|
||||
|
||||
For more information, on how to customize this file, please see
|
||||
http://wiki.apache.org/solr/SchemaXml
|
||||
-->
|
||||
|
||||
<schema name="example" version="1.1">
|
||||
<!-- attribute "name" is the name of this schema and is only used for display purposes.
|
||||
Applications should change this to reflect the nature of the search collection.
|
||||
version="1.1" is Solr's version number for the schema syntax and semantics. It should
|
||||
not normally be changed by applications.
|
||||
1.0: multiValued attribute did not exist, all fields are multiValued by nature
|
||||
1.1: multiValued attribute introduced, false by default -->
|
||||
|
||||
|
||||
<!-- field type definitions. The "name" attribute is
|
||||
just a label to be used by field definitions. The "class"
|
||||
attribute and any other attributes determine the real
|
||||
behavior of the fieldType.
|
||||
Class names starting with "solr" refer to java classes in the
|
||||
org.apache.solr.analysis package.
|
||||
-->
|
||||
|
||||
<!-- The StrField type is not analyzed, but indexed/stored verbatim.
|
||||
- StrField and TextField support an optional compressThreshold which
|
||||
limits compression (if enabled in the derived fields) to values which
|
||||
exceed a certain size (in characters).
|
||||
-->
|
||||
<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
<!-- boolean type: "true" or "false" -->
|
||||
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
<!-- The optional sortMissingLast and sortMissingFirst attributes are
|
||||
currently supported on types that are sorted internally as strings.
|
||||
- If sortMissingLast="true", then a sort on this field will cause documents
|
||||
without the field to come after documents with the field,
|
||||
regardless of the requested sort order (asc or desc).
|
||||
- If sortMissingFirst="true", then a sort on this field will cause documents
|
||||
without the field to come before documents with the field,
|
||||
regardless of the requested sort order.
|
||||
- If sortMissingLast="false" and sortMissingFirst="false" (the default),
|
||||
then default lucene sorting will be used which places docs without the
|
||||
field first in an ascending sort and last in a descending sort.
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Default numeric field types. For faster range queries, consider the tint/tfloat/tlong/tdouble types.
|
||||
-->
|
||||
<fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
|
||||
<fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
|
||||
|
||||
|
||||
<!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
|
||||
is a more restricted form of the canonical representation of dateTime
|
||||
http://www.w3.org/TR/xmlschema-2/#dateTime
|
||||
The trailing "Z" designates UTC time and is mandatory.
|
||||
Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
|
||||
All other components are mandatory.
|
||||
|
||||
Expressions can also be used to denote calculations that should be
|
||||
performed relative to "NOW" to determine the value, ie...
|
||||
|
||||
NOW/HOUR
|
||||
... Round to the start of the current hour
|
||||
NOW-1DAY
|
||||
... Exactly 1 day prior to now
|
||||
NOW/DAY+6MONTHS+3DAYS
|
||||
... 6 months and 3 days in the future from the start of
|
||||
the current day
|
||||
|
||||
Consult the TrieDateField javadocs for more information.
|
||||
-->
|
||||
<fieldType name="date" class="solr.TrieDateField" sortMissingLast="true" omitNorms="true"/>
|
||||
|
||||
|
||||
<!-- The "RandomSortField" is not used to store or search any
|
||||
data. You can declare fields of this type it in your schema
|
||||
to generate psuedo-random orderings of your docs for sorting
|
||||
purposes. The ordering is generated based on the field name
|
||||
and the version of the index, As long as the index version
|
||||
remains unchanged, and the same field name is reused,
|
||||
the ordering of the docs will be consistent.
|
||||
If you want differend psuedo-random orderings of documents,
|
||||
for the same version of the index, use a dynamicField and
|
||||
change the name
|
||||
-->
|
||||
<fieldType name="random" class="solr.RandomSortField" indexed="true" />
|
||||
|
||||
<!-- solr.TextField allows the specification of custom text analyzers
|
||||
specified as a tokenizer and a list of token filters. Different
|
||||
analyzers may be specified for indexing and querying.
|
||||
|
||||
The optional positionIncrementGap puts space between multiple fields of
|
||||
this type on the same document, with the purpose of preventing false phrase
|
||||
matching across fields.
|
||||
|
||||
For more info on customizing your analyzer chain, please see
|
||||
http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
|
||||
-->
|
||||
|
||||
<!-- One can also specify an existing Analyzer class that has a
|
||||
default constructor via the class attribute on the analyzer element
|
||||
<fieldType name="text_greek" class="solr.TextField">
|
||||
<analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
|
||||
</fieldType>
|
||||
-->
|
||||
|
||||
<!-- A text field that only splits on whitespace for exact matching of words -->
|
||||
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- A text field that uses WordDelimiterFilter to enable splitting and matching of
|
||||
words on case-change, alpha numeric boundaries, and non-alphanumeric chars,
|
||||
so that a query of "wifi" or "wi fi" could match a document containing "Wi-Fi".
|
||||
Synonyms and stopwords are customized by external files, and stemming is enabled.
|
||||
Duplicate tokens at the same position (which may result from Stemmed Synonyms or
|
||||
WordDelim parts) are removed.
|
||||
-->
|
||||
<fieldType name="text" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer type="index">
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<!-- in this example, we will only use synonyms at query time
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
|
||||
-->
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.PorterStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
<analyzer type="query">
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.PorterStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
|
||||
<fieldType name="text_plain" class="solr.TextField" positionIncrementGap="100">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.ICUTokenizerFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- Less flexible matching, but less false matches. Probably not ideal for product names,
|
||||
but may be good for SKUs. Can insert dashes in the wrong place and still match. -->
|
||||
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
|
||||
<analyzer>
|
||||
<tokenizer class="solr.MockTokenizerFactory"/>
|
||||
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
|
||||
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
||||
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
|
||||
<filter class="solr.EnglishMinimalStemFilterFactory"/>
|
||||
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- This is an example of using the KeywordTokenizer along
|
||||
With various TokenFilterFactories to produce a sortable field
|
||||
that does not include some properties of the source text
|
||||
-->
|
||||
<fieldType name="alphaOnlySort" class="solr.TextField" sortMissingLast="true" omitNorms="true">
|
||||
<analyzer>
|
||||
<!-- KeywordTokenizer does no actual tokenizing, so the entire
|
||||
input string is preserved as a single token
|
||||
-->
|
||||
<tokenizer class="solr.MockTokenizerFactory" pattern="keyword"/>
|
||||
<!-- The LowerCase TokenFilter does what you expect, which can be
|
||||
when you want your sorting to be case insensitive
|
||||
-->
|
||||
<filter class="solr.LowerCaseFilterFactory" />
|
||||
<!-- The TrimFilter removes any leading or trailing whitespace -->
|
||||
<filter class="solr.TrimFilterFactory" />
|
||||
<!-- The PatternReplaceFilter gives you the flexibility to use
|
||||
Java Regular expression to replace any sequence of characters
|
||||
matching a pattern with an arbitrary replacement string,
|
||||
which may include back refrences to portions of the orriginal
|
||||
string matched by the pattern.
|
||||
|
||||
See the Java Regular Expression documentation for more
|
||||
infomation on pattern and replacement string syntax.
|
||||
|
||||
http://docs.oracle.com/javase/7/docs/api/java/util/regex/package-summary.html
|
||||
-->
|
||||
<filter class="solr.PatternReplaceFilterFactory"
|
||||
pattern="([^a-z])" replacement="" replace="all"
|
||||
/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
<!-- since fields of this type are by default not stored or indexed, any data added to
|
||||
them will be ignored outright
|
||||
-->
|
||||
<fieldType name="ignored" stored="false" indexed="false" class="solr.StrField" />
|
||||
|
||||
<fieldType name="file" keyField="id" defVal="1" stored="false" indexed="false" class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
<fieldType name="sfile" keyField="sfile_s" defVal="1" stored="false" indexed="false" class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
|
||||
<fieldType name="tint" class="solr.TrieIntField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tfloat" class="solr.TrieFloatField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tlong" class="solr.TrieLongField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdouble" class="solr.TrieDoubleField" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdouble4" class="solr.TrieDoubleField" precisionStep="4" omitNorms="true" positionIncrementGap="0"/>
|
||||
<fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" positionIncrementGap="0"/>
|
||||
|
||||
|
||||
<fieldType name="tints" class="solr.TrieIntField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
<fieldType name="tfloats" class="solr.TrieFloatField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true"/>
|
||||
<fieldType name="tlongs" class="solr.TrieLongField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true"/>
|
||||
<fieldType name="tdoubles" class="solr.TrieDoubleField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
<fieldType name="tdates" class="solr.TrieDateField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" />
|
||||
|
||||
<!-- Poly field -->
|
||||
<fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/>
|
||||
<fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
|
||||
<fieldType name="geohash" class="solr.GeoHashField"/>
|
||||
|
||||
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
|
||||
|
||||
<!-- A specialized field for geospatial search. If indexed, this fieldType must not be multi
|
||||
valued. -->
|
||||
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
|
||||
|
||||
<!-- These should pass right through and insure that we can declare external field types -->
|
||||
<fieldType name="eff_float" keyField="id" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField" valType="float"/>
|
||||
|
||||
<fieldType name="eff_tfloat" keyField="eff_ti" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField" valType="tfloat"/>
|
||||
|
||||
<!-- Be sure that the valType can be optional Since valType has done nothing up until now, this is preferred -->
|
||||
<fieldType name="eff_none" keyField="id" defVal="0"
|
||||
stored="false" indexed="true"
|
||||
class="solr.ExternalFileField"/>
|
||||
|
||||
<fieldType name="text_no_analyzer" stored="false" indexed="true" class="solr.TextField" />
|
||||
|
||||
<fieldType name="text_length" class="solr.TextField">
|
||||
<analyzer>
|
||||
<tokenizer class="solr.StandardTokenizerFactory"/>
|
||||
<filter class="solr.StandardFilterFactory"/>
|
||||
<filter class="solr.LowerCaseFilterFactory"/>
|
||||
<filter class="solr.LengthFilterFactory" min="2" max="32768"/>
|
||||
</analyzer>
|
||||
</fieldType>
|
||||
|
||||
|
||||
|
||||
<!-- Valid attributes for fields:
|
||||
name: mandatory - the name for the field
|
||||
type: mandatory - the name of a previously defined type from the <types> section
|
||||
indexed: true if this field should be indexed (searchable or sortable)
|
||||
stored: true if this field should be retrievable
|
||||
multiValued: true if this field may contain multiple values per document
|
||||
omitNorms: (expert) set to true to omit the norms associated with
|
||||
this field (this disables length normalization and index-time
|
||||
boosting for the field, and saves some memory). Only full-text
|
||||
fields or fields that need an index-time boost need norms.
|
||||
termVectors: [false] set to true to store the term vector for a given field.
|
||||
When using MoreLikeThis, fields used for similarity should be stored for
|
||||
best performance.
|
||||
-->
|
||||
|
||||
<!-- for testing, a type that does a transform to see if it's correctly done everywhere -->
|
||||
<field name="id" type="float" indexed="true" stored="true" required="true" />
|
||||
<field name="FINGERPRINT" type="text_plain" indexed="true" omitNorms="true" stored="false" multiValued="false" required="false" docValues="false"/>
|
||||
<field name="ACLID" type="long" indexed="true" stored="true" docValues="true" />
|
||||
<field name="READER" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="DENIED" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="DOC_TYPE" type="string" indexed="true" stored="true" docValues="true" />
|
||||
|
||||
<field name="text" type="text" indexed="true" stored="false" />
|
||||
|
||||
<!-- Test a point field for distances -->
|
||||
<field name="point" type="xy" indexed="true" stored="true" multiValued="false"/>
|
||||
<field name="pointD" type="xyd" indexed="true" stored="true" multiValued="false"/>
|
||||
|
||||
<field name="point_hash" type="geohash" indexed="true" stored="true" multiValued="false"/>
|
||||
|
||||
<field name="signatureField" type="string" indexed="true" stored="false"/>
|
||||
|
||||
<field name="eff_trie" type="eff_tfloat" />
|
||||
|
||||
<field name="text_no_analyzer" type="text_no_analyzer" indexed="true" />
|
||||
|
||||
<field name="_version_" type="long" indexed="true" stored="true" multiValued="false" />
|
||||
|
||||
<field name="cat" type="string" indexed="true" stored="true" multiValued="true"/>
|
||||
<field name="cat_docValues" type="string" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_intDocValues" type="int" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_floatDocValues" type="float" indexed="true" stored="true" docValues="true" multiValued="true" />
|
||||
<field name="cat_length" type="text_length" indexed="true" stored="true" multiValued="true"/>
|
||||
|
||||
|
||||
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
|
||||
will be used if the name matches any of the patterns.
|
||||
RESTRICTION: the glob-like pattern in the name attribute must have
|
||||
a "*" only at the start or the end.
|
||||
EXAMPLE: name="*_i" will match any field ending in _i (like myid_i, z_i)
|
||||
Longer patterns will be matched first. if equal size patterns
|
||||
both match, the first appearing in the schema will be used. -->
|
||||
<dynamicField name="*_s" type="string" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_s_dv" type="string" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_ss" type="string" indexed="true" stored="true" multiValued="true"/>
|
||||
<dynamicField name="*_sS" type="string" indexed="false" stored="true"/>
|
||||
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ii" type="int" indexed="true" stored="true" multiValued="true"/>
|
||||
<dynamicField name="*_l" type="long" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_f" type="float" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_d" type="double" indexed="true" stored="true"/>
|
||||
|
||||
<dynamicField name="*_ti" type="tint" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ti_dv" type="int" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_ti_ni_dv" type="int" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tl" type="tlong" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tl_dv" type="tlong" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tl_ni_dv" type="tlong" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tf" type="tfloat" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tf_dv" type="tfloat" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tf_ni_dv" type="tfloat" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_td" type="tdouble" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_td_dv" type="tdouble" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_td_ni_dv" type="tdouble" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdt" type="tdate" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tdt_dv" type="tdate" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdt_ni_dv" type="tdate" indexed="false" stored="true" docValues="true"/>
|
||||
|
||||
<dynamicField name="*_tis" type="tints" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tis_dv" type="tints" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tis_ni_dv" type="tints" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tls" type="tlongs" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tls_dv" type="tlongs" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tls_ni_dv" type="tlongs" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tfs" type="tfloats" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tfs_dv" type="tfloats" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tfs_ni_dv" type="tfloats" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tds" type="tdoubles" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tds_dv" type="tdoubles" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tds_ni_dv" type="tdoubles" indexed="false" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdts" type="tdates" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_tdts_dv" type="tdates" indexed="true" stored="true" docValues="true"/>
|
||||
<dynamicField name="*_tdts_ni_dv" type="tdates" indexed="false" stored="true" docValues="true"/>
|
||||
|
||||
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
|
||||
<dynamicField name="*_ws" type="text_ws" indexed="true" stored="true"/>
|
||||
|
||||
<dynamicField name="*_extf" type="file"/>
|
||||
<dynamicField name="*_extfs" type="sfile"/>
|
||||
|
||||
<dynamicField name="*_random" type="random" />
|
||||
|
||||
<!-- uncomment the following to ignore any fields that don't already match an existing
|
||||
field name or dynamic field, rather than reporting them as an error.
|
||||
alternately, change the type="ignored" to some other type e.g. "text" if you want
|
||||
unknown fields indexed and/or stored by default -->
|
||||
<!--dynamicField name="*" type="ignored" /-->
|
||||
|
||||
|
||||
<!-- Field to use to determine and enforce document uniqueness.
|
||||
Unless this field is marked with required="false", it will be a required field
|
||||
-->
|
||||
<uniqueKey>id</uniqueKey>
|
||||
|
||||
</schema>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!--
|
||||
|
||||
A solrconfig.xml snippet containing indexConfig settings for randomized testing.
|
||||
|
||||
-->
|
||||
<indexConfig>
|
||||
<!-- this sys property is not set by SolrTestCaseJ4 because we ideally want to use
|
||||
the RandomMergePolicy in all tests - but some tests expect very specific
|
||||
Merge behavior, so those tests can set it as needed.
|
||||
-->
|
||||
<mergePolicy class="${solr.tests.mergePolicy:org.apache.solr.util.RandomMergePolicy}" />
|
||||
|
||||
<useCompoundFile>${useCompoundFile:false}</useCompoundFile>
|
||||
|
||||
<maxBufferedDocs>${solr.tests.maxBufferedDocs}</maxBufferedDocs>
|
||||
<maxIndexingThreads>${solr.tests.maxIndexingThreads}</maxIndexingThreads>
|
||||
<ramBufferSizeMB>${solr.tests.ramBufferSizeMB}</ramBufferSizeMB>
|
||||
|
||||
<mergeScheduler class="${solr.tests.mergeScheduler}" />
|
||||
|
||||
<writeLockTimeout>1000</writeLockTimeout>
|
||||
<commitLockTimeout>10000</commitLockTimeout>
|
||||
|
||||
<!-- this sys property is not set by SolrTestCaseJ4 because almost all tests should
|
||||
use the single process lockType for speed - but tests that explicitly need
|
||||
to vary the lockType canset it as needed.
|
||||
-->
|
||||
<lockType>${solr.tests.lockType:single}</lockType>
|
||||
</indexConfig>
|
||||
+567
@@ -0,0 +1,567 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- This is a "kitchen sink" config file that tests can use.
|
||||
When writting a new test, feel free to add *new* items (plugins,
|
||||
config options, etc...) as long as they don't break any existing
|
||||
tests. if you need to test something esoteric please add a new
|
||||
"solrconfig-your-esoteric-purpose.xml" config file.
|
||||
|
||||
Note in particular that this test is used by MinimalSchemaTest so
|
||||
Anything added to this file needs to work correctly even if there
|
||||
is now uniqueKey or defaultSearch Field.
|
||||
-->
|
||||
|
||||
<config>
|
||||
|
||||
<jmx />
|
||||
|
||||
<!-- Used to specify an alternate directory to hold all index data.
|
||||
It defaults to "index" if not present, and should probably
|
||||
not be changed if replication is in use. -->
|
||||
<dataDir>${solr.data.dir:}</dataDir>
|
||||
|
||||
<!-- The DirectoryFactory to use for indexes.
|
||||
solr.StandardDirectoryFactory, the default, is filesystem based.
|
||||
solr.RAMDirectoryFactory is memory based and not persistent. -->
|
||||
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}">
|
||||
<double name="maxWriteMBPerSecDefault">1000000</double>
|
||||
<double name="maxWriteMBPerSecFlush">2000000</double>
|
||||
<double name="maxWriteMBPerSecMerge">3000000</double>
|
||||
<double name="maxWriteMBPerSecRead">4000000</double>
|
||||
</directoryFactory>
|
||||
|
||||
<schemaFactory class="ClassicIndexSchemaFactory"/>
|
||||
|
||||
<luceneMatchVersion>${tests.luceneMatchVersion:LUCENE_CURRENT}</luceneMatchVersion>
|
||||
|
||||
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
|
||||
<updateHandler class="solr.DirectUpdateHandler2">
|
||||
|
||||
<!-- autocommit pending docs if certain criteria are met
|
||||
<autoCommit>
|
||||
<maxDocs>10000</maxDocs>
|
||||
<maxTime>3600000</maxTime>
|
||||
</autoCommit>
|
||||
-->
|
||||
<!-- represents a lower bound on the frequency that commits may
|
||||
occur (in seconds). NOTE: not yet implemented
|
||||
|
||||
<commitIntervalLowerBound>0</commitIntervalLowerBound>
|
||||
-->
|
||||
|
||||
<!-- The RunExecutableListener executes an external command.
|
||||
exe - the name of the executable to run
|
||||
dir - dir to use as the current working directory. default="."
|
||||
wait - the calling thread waits until the executable returns. default="true"
|
||||
args - the arguments to pass to the program. default=nothing
|
||||
env - environment variables to set. default=nothing
|
||||
-->
|
||||
<!-- A postCommit event is fired after every commit
|
||||
<listener event="postCommit" class="solr.RunExecutableListener">
|
||||
<str name="exe">/var/opt/resin3/__PORT__/scripts/solr/snapshooter</str>
|
||||
<str name="dir">/var/opt/resin3/__PORT__</str>
|
||||
<bool name="wait">true</bool>
|
||||
<arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
|
||||
<arr name="env"> <str>MYVAR=val1</str> </arr>
|
||||
</listener>
|
||||
-->
|
||||
<!--
|
||||
<updateLog enable="${enable.update.log:true}">
|
||||
<str name="dir">${solr.ulog.dir:}</str>
|
||||
</updateLog>
|
||||
|
||||
<commitWithin>
|
||||
<softCommit>${solr.commitwithin.softcommit:true}</softCommit>
|
||||
</commitWithin>
|
||||
-->
|
||||
|
||||
</updateHandler>
|
||||
|
||||
<query>
|
||||
<!-- Maximum number of clauses in a boolean query... can affect
|
||||
range or wildcard queries that expand to big boolean
|
||||
queries. An exception is thrown if exceeded.
|
||||
-->
|
||||
<maxBooleanClauses>1024</maxBooleanClauses>
|
||||
|
||||
<!-- Cache specification for Filters or DocSets - unordered set of *all* documents
|
||||
that match a particular query.
|
||||
-->
|
||||
<filterCache
|
||||
class="solr.search.FastLRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="2"/>
|
||||
|
||||
<queryResultCache
|
||||
class="solr.search.LRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="2"/>
|
||||
|
||||
<documentCache
|
||||
class="solr.search.LRUCache"
|
||||
size="512"
|
||||
initialSize="512"
|
||||
autowarmCount="0"/>
|
||||
|
||||
<cache name="perSegFilter"
|
||||
class="solr.search.LRUCache"
|
||||
size="10"
|
||||
initialSize="0"
|
||||
autowarmCount="10" />
|
||||
|
||||
<!-- If true, stored fields that are not requested will be loaded lazily.
|
||||
-->
|
||||
<enableLazyFieldLoading>true</enableLazyFieldLoading>
|
||||
|
||||
<!--
|
||||
|
||||
<cache name="myUserCache"
|
||||
class="solr.search.LRUCache"
|
||||
size="4096"
|
||||
initialSize="1024"
|
||||
autowarmCount="1024"
|
||||
regenerator="MyRegenerator"
|
||||
/>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<useFilterForSortedQuery>true</useFilterForSortedQuery>
|
||||
-->
|
||||
|
||||
<queryResultWindowSize>10</queryResultWindowSize>
|
||||
|
||||
<!-- set maxSize artificially low to exercise both types of sets -->
|
||||
<HashDocSet maxSize="3" loadFactor="0.75"/>
|
||||
|
||||
<!-- boolToFilterOptimizer converts boolean clauses with zero boost
|
||||
into cached filters if the number of docs selected by the clause exceeds
|
||||
the threshold (represented as a fraction of the total index)
|
||||
-->
|
||||
<boolTofilterOptimizer enabled="false" cacheSize="32" threshold=".05"/>
|
||||
|
||||
<!-- a newSearcher event is fired whenever a new searcher is being prepared
|
||||
and there is a current searcher handling requests (aka registered). -->
|
||||
<!-- QuerySenderListener takes an array of NamedList and executes a
|
||||
local query request for each NamedList in sequence. -->
|
||||
<!--
|
||||
<listener event="newSearcher" class="solr.QuerySenderListener">
|
||||
<arr name="queries">
|
||||
<lst> <str name="q">solr</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
<lst> <str name="q">rocks</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
</arr>
|
||||
</listener>
|
||||
-->
|
||||
|
||||
<!-- a firstSearcher event is fired whenever a new searcher is being
|
||||
prepared but there is no current registered searcher to handle
|
||||
requests or to gain prewarming data from. -->
|
||||
<!--
|
||||
<listener event="firstSearcher" class="solr.QuerySenderListener">
|
||||
<arr name="queries">
|
||||
<lst> <str name="q">fast_warm</str> <str name="start">0</str> <str name="rows">10</str> </lst>
|
||||
</arr>
|
||||
</listener>
|
||||
-->
|
||||
<listener event="firstSearcher" class="org.alfresco.solr.lifecycle.SolrCoreLoadListener" />
|
||||
</query>
|
||||
|
||||
<queryResponseWriter name="xml" default="true"
|
||||
class="solr.XMLResponseWriter" />
|
||||
|
||||
<requestHandler name="/replication" class="org.alfresco.solr.handler.AlfrescoReplicationHandler">
|
||||
<lst name="slave">
|
||||
<str name="masterUrl">{masterURL}</str>
|
||||
<str name="pollInterval">00:00:02</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<!-- An alternate set representation that uses an integer hash to store filters (sets of docids).
|
||||
If the set cardinality <= maxSize elements, then HashDocSet will be used instead of the bitset
|
||||
based HashBitset. -->
|
||||
|
||||
<!-- requestHandler plugins... incoming queries will be dispatched to the
|
||||
correct handler based on the 'qt' param matching the
|
||||
name of registered handlers.
|
||||
The "standard" request handler is the default and will be used if qt
|
||||
is not specified in the request.
|
||||
-->
|
||||
<requestHandler name="standard" class="solr.StandardRequestHandler">
|
||||
<bool name="httpCaching">true</bool>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/get" class="solr.RealTimeGetHandler">
|
||||
<lst name="defaults">
|
||||
<str name="omitHeader">true</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="dismax" class="solr.SearchHandler" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">dismax</str>
|
||||
<str name="q.alt">*:*</str>
|
||||
<float name="tie">0.01</float>
|
||||
<str name="qf">
|
||||
text^0.5 features_t^1.0 subject^1.4 title_stemmed^2.0
|
||||
</str>
|
||||
<str name="pf">
|
||||
text^0.2 features_t^1.1 subject^1.4 title_stemmed^2.0 title^1.5
|
||||
</str>
|
||||
<str name="bf">
|
||||
ord(weight)^0.5 recip(rord(iind),1,1000,1000)^0.3
|
||||
</str>
|
||||
<str name="mm">
|
||||
3<-1 5<-2 6<90%
|
||||
</str>
|
||||
<int name="ps">100</int>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<!-- test query parameter defaults -->
|
||||
<requestHandler name="defaults" class="solr.StandardRequestHandler">
|
||||
<lst name="defaults">
|
||||
<int name="rows">4</int>
|
||||
<bool name="hl">true</bool>
|
||||
<str name="hl.fl">text,name,subject,title,whitetok</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<!-- test query parameter defaults -->
|
||||
<requestHandler name="lazy" class="solr.StandardRequestHandler" startup="lazy">
|
||||
<lst name="defaults">
|
||||
<int name="rows">4</int>
|
||||
<bool name="hl">true</bool>
|
||||
<str name="hl.fl">text,name,subject,title,whitetok</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/update" class="solr.UpdateRequestHandler" />
|
||||
|
||||
<searchComponent name="fingerprint" class="org.alfresco.solr.component.FingerPrintComponent"/>
|
||||
|
||||
<requestHandler name="/fingerprint" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<arr name="components">
|
||||
<str>fingerprint</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/afts" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">afts</str>
|
||||
<str name="spellcheck">false</str>
|
||||
<str name="spellcheck.extendedResults">false</str>
|
||||
<str name="spellcheck.count">5</str>
|
||||
<str name="spellcheck.alternativeTermCount">2</str>
|
||||
<str name="spellcheck.maxResultsForSuggest">5</str>
|
||||
<str name="spellcheck.collate">true</str>
|
||||
<str name="spellcheck.collateExtendedResults">true</str>
|
||||
<str name="spellcheck.maxCollationTries">5</str>
|
||||
<str name="spellcheck.maxCollations">3</str>
|
||||
<str name="carrot.title">mltext@m___t@{http://www.alfresco.org/model/content/1.0}title</str>
|
||||
<str name="carrot.url">id</str>
|
||||
<str name="carrot.snippet">content@s___t@{http://www.alfresco.org/model/content/1.0}content</str>
|
||||
<bool name="carrot.produceSummary">true</bool>
|
||||
<bool name="carrot.outputSubClusters">false</bool>
|
||||
</lst>
|
||||
<arr name="components">
|
||||
<str>setLocale</str>
|
||||
<str>rewriteFacetParameters</str>
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>facet_module</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
<str>clearLocale</str>
|
||||
<str>rewriteFacetCounts</str>
|
||||
<str>spellcheck</str>
|
||||
<str>spellcheckbackcompat</str>
|
||||
<str>setProcessedDenies</str>
|
||||
</arr>
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/native" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<!-- default values for query parameters can be specified, these
|
||||
will be overridden by parameters in the request
|
||||
-->
|
||||
<lst name="defaults">
|
||||
<str name="echoParams">explicit</str>
|
||||
<int name="rows">10</int>
|
||||
<str name="df">suggest</str>
|
||||
</lst>
|
||||
|
||||
|
||||
<arr name="components">
|
||||
<str>setLocale</str>
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
<str>clearLocale</str>
|
||||
</arr>
|
||||
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/cmis" class="org.apache.solr.handler.component.AlfrescoSearchHandler" lazy="true" >
|
||||
<lst name="defaults">
|
||||
<str name="defType">cmis</str>
|
||||
</lst>
|
||||
<arr name="components">
|
||||
<str>query</str>
|
||||
<str>facet</str>
|
||||
<str>mlt</str>
|
||||
<str>highlight</str>
|
||||
<str>stats</str>
|
||||
<str>debug</str>
|
||||
</arr>
|
||||
<shardHandlerFactory class="org.apache.solr.handler.component.AlfrescoHttpShardHandlerFactory" />
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent name="termsComp" class="org.apache.solr.handler.component.TermsComponent"/>
|
||||
|
||||
<requestHandler name="/terms" class="org.apache.solr.handler.component.SearchHandler">
|
||||
<arr name="components">
|
||||
<str>termsComp</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="mltrh" class="org.apache.solr.handler.component.SearchHandler">
|
||||
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent name="tvComponent" class="org.apache.solr.handler.component.TermVectorComponent"/>
|
||||
|
||||
<requestHandler name="tvrh" class="org.apache.solr.handler.component.SearchHandler">
|
||||
<lst name="defaults">
|
||||
|
||||
</lst>
|
||||
<arr name="last-components">
|
||||
<str>tvComponent</str>
|
||||
</arr>
|
||||
</requestHandler>
|
||||
|
||||
<requestHandler name="/mlt" class="solr.MoreLikeThisHandler">
|
||||
</requestHandler>
|
||||
|
||||
<searchComponent class="solr.HighlightComponent" name="highlight">
|
||||
<highlighting class="org.apache.solr.handler.component.AlfrescoSolrHighlighter">
|
||||
<!-- Configure the standard fragmenter -->
|
||||
<fragmenter name="gap" class="org.apache.solr.highlight.GapFragmenter" default="true">
|
||||
<lst name="defaults">
|
||||
<int name="hl.fragsize">100</int>
|
||||
</lst>
|
||||
</fragmenter>
|
||||
|
||||
<fragmenter name="regex" class="org.apache.solr.highlight.RegexFragmenter">
|
||||
<lst name="defaults">
|
||||
<!-- slightly smaller fragsizes work better because of slop -->
|
||||
<int name="hl.fragsize">70</int>
|
||||
<!-- allow 50% slop on fragment sizes -->
|
||||
<float name="hl.regex.slop">0.5</float>
|
||||
<!-- a basic sentence pattern -->
|
||||
<str name="hl.regex.pattern">[-\w ,/\n\"']{20,200}</str>
|
||||
</lst>
|
||||
</fragmenter>
|
||||
|
||||
<!-- Configure the standard formatter -->
|
||||
<formatter name="html" class="org.apache.solr.highlight.HtmlFormatter" default="true">
|
||||
<lst name="defaults">
|
||||
<str name="hl.simple.pre"><![CDATA[<em>]]></str>
|
||||
<str name="hl.simple.post"><![CDATA[</em>]]></str>
|
||||
</lst>
|
||||
</formatter>
|
||||
|
||||
<!-- Configure the standard encoder -->
|
||||
<encoder name="html" class="solr.highlight.HtmlEncoder"/>
|
||||
|
||||
<!-- Configure the standard fragListBuilder -->
|
||||
<fragListBuilder name="simple" class="solr.highlight.SimpleFragListBuilder"/>
|
||||
|
||||
<!-- Configure the single fragListBuilder -->
|
||||
<fragListBuilder name="single" class="solr.highlight.SingleFragListBuilder"/>
|
||||
|
||||
<!-- Configure the weighted fragListBuilder -->
|
||||
<fragListBuilder name="weighted" default="true" class="solr.highlight.WeightedFragListBuilder"/>
|
||||
|
||||
<!-- default tag FragmentsBuilder -->
|
||||
<fragmentsBuilder name="default" default="true" class="solr.highlight.ScoreOrderFragmentsBuilder">
|
||||
<!--
|
||||
<lst name="defaults">
|
||||
<str name="hl.multiValuedSeparatorChar">/</str>
|
||||
</lst>
|
||||
-->
|
||||
</fragmentsBuilder>
|
||||
|
||||
<!-- multi-colored tag FragmentsBuilder -->
|
||||
<fragmentsBuilder name="colored" class="solr.highlight.ScoreOrderFragmentsBuilder">
|
||||
<lst name="defaults">
|
||||
<str name="hl.tag.pre"><![CDATA[
|
||||
<b style="background:yellow">,<b style="background:lawgreen">,
|
||||
<b style="background:aquamarine">,<b style="background:magenta">,
|
||||
<b style="background:palegreen">,<b style="background:coral">,
|
||||
<b style="background:wheat">,<b style="background:khaki">,
|
||||
<b style="background:lime">,<b style="background:deepskyblue">]]></str>
|
||||
<str name="hl.tag.post"><![CDATA[</b>]]></str>
|
||||
</lst>
|
||||
</fragmentsBuilder>
|
||||
|
||||
<boundaryScanner name="default" default="true" class="solr.highlight.SimpleBoundaryScanner">
|
||||
<lst name="defaults">
|
||||
<str name="hl.bs.maxScan">10</str>
|
||||
<str name="hl.bs.chars">.,!? 	 </str>
|
||||
</lst>
|
||||
</boundaryScanner>
|
||||
|
||||
<boundaryScanner name="breakIterator" class="solr.highlight.BreakIteratorBoundaryScanner">
|
||||
<lst name="defaults">
|
||||
<!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
|
||||
<str name="hl.bs.type">WORD</str>
|
||||
<!-- language and country are used when constructing Locale object. -->
|
||||
<!-- And the Locale object will be used when getting instance of BreakIterator -->
|
||||
<str name="hl.bs.language">en</str>
|
||||
<str name="hl.bs.country">US</str>
|
||||
</lst>
|
||||
</boundaryScanner>
|
||||
</highlighting>
|
||||
</searchComponent>
|
||||
|
||||
<!-- enable streaming for testing... -->
|
||||
<requestDispatcher handleSelect="true" >
|
||||
<requestParsers enableRemoteStreaming="true" multipartUploadLimitInKB="2048" />
|
||||
<httpCaching lastModifiedFrom="openTime" etagSeed="Solr" never304="false">
|
||||
<cacheControl>max-age=30, public</cacheControl>
|
||||
</httpCaching>
|
||||
</requestDispatcher>
|
||||
|
||||
<!-- Echo the request contents back to the client -->
|
||||
<requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
|
||||
<lst name="defaults">
|
||||
<str name="echoParams">explicit</str>
|
||||
<str name="echoHandler">true</str>
|
||||
</lst>
|
||||
</requestHandler>
|
||||
|
||||
<admin>
|
||||
<defaultQuery>solr</defaultQuery>
|
||||
<gettableFiles>solrconfig.xml schema.xml admin-extra.html</gettableFiles>
|
||||
</admin>
|
||||
|
||||
<!-- test getting system property -->
|
||||
<!--
|
||||
<propTest attr1="${solr.test.sys.prop1}-$${literal}"
|
||||
attr2="${non.existent.sys.prop:default-from-config}">prefix-${solr.test.sys.prop2}-suffix</propTest>
|
||||
-->
|
||||
|
||||
<queryParser name="alfrescoReRank" class="org.alfresco.solr.query.AlfrescoReRankQParserPlugin"/>
|
||||
<queryParser name="afts" class="org.alfresco.solr.query.AlfrescoFTSQParserPlugin"/>
|
||||
|
||||
<!--
|
||||
<queryParser name="afts" class="org.alfresco.solr.query.AlfrescoFTSQParserPlugin">
|
||||
<str name="rerankPhase">SINGLE_PASS</str>
|
||||
</queryParser>
|
||||
-->
|
||||
|
||||
<queryParser name="cmis" class="org.alfresco.solr.query.CmisQParserPlugin"/>
|
||||
|
||||
<xi:include href="solrconfig_insight.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:fallback/>
|
||||
</xi:include>
|
||||
|
||||
<queryParser name="mimetype" class="org.alfresco.solr.query.MimetypeGroupingQParserPlugin" >
|
||||
<str name="mapping">conf/mime_types.csv</str>
|
||||
</queryParser>
|
||||
|
||||
<queryParser name="contentSize" class="org.alfresco.solr.query.ContentSizeGroupingQParserPlugin" >
|
||||
<int name="scale">1</int>
|
||||
<int name="buckets">10</int>
|
||||
</queryParser>
|
||||
<searchComponent name="setLocale" class="org.alfresco.solr.component.SetLocaleComponent" />
|
||||
<searchComponent name="clearLocale" class="org.alfresco.solr.component.ClearLocaleComponent" />
|
||||
|
||||
<!-- Spell Check
|
||||
|
||||
The spell check component can return a list of alternative spelling
|
||||
suggestions.
|
||||
|
||||
http://wiki.apache.org/solr/SpellCheckComponent
|
||||
-->
|
||||
<searchComponent name="spellcheck" class="org.alfresco.solr.component.spellcheck.AlfrescoSpellCheckComponent">
|
||||
|
||||
<str name="queryAnalyzerFieldType">text_shingle</str>
|
||||
|
||||
<!-- Multiple "Spell Checkers" can be declared and used by this
|
||||
component
|
||||
-->
|
||||
|
||||
<!-- a spellchecker built from a field of the main index -->
|
||||
<lst name="spellchecker">
|
||||
<str name="name">default</str>
|
||||
<str name="field">suggest</str>
|
||||
<str name="classname">solr.DirectSolrSpellChecker</str>
|
||||
<!-- the spellcheck distance measure used, the default is the internal levenshtein -->
|
||||
<str name="distanceMeasure">internal</str>
|
||||
<!-- minimum accuracy needed to be considered a valid spellcheck suggestion -->
|
||||
<float name="accuracy">0.5</float>
|
||||
<!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 -->
|
||||
<int name="maxEdits">2</int>
|
||||
<!-- the minimum shared prefix when enumerating terms -->
|
||||
<int name="minPrefix">1</int>
|
||||
<!-- maximum number of inspections per result. -->
|
||||
<int name="maxInspections">5</int>
|
||||
<!-- minimum length of a query term to be considered for correction -->
|
||||
<int name="minQueryLength">4</int>
|
||||
<!-- maximum threshold of documents a query term can appear to be considered for correction -->
|
||||
<float name="maxQueryFrequency">0.01</float>
|
||||
<!-- uncomment this to require suggestions to occur in 1% of the documents
|
||||
<float name="thresholdTokenFrequency">.01</float>
|
||||
-->
|
||||
</lst>
|
||||
|
||||
<!-- word break -->
|
||||
<lst name="spellchecker">
|
||||
<str name="name">wordbreak</str>
|
||||
<str name="field">suggest</str>
|
||||
<str name="classname">solr.WordBreakSolrSpellChecker</str>
|
||||
<str name="combineWords">true</str>
|
||||
<str name="breakWords">true</str>
|
||||
<int name="maxChanges">10</int>
|
||||
<int name="minBreakLength">5</int>
|
||||
</lst>
|
||||
|
||||
</searchComponent>
|
||||
<searchComponent name="spellcheckbackcompat" class="org.alfresco.solr.component.spellcheck.AlfrescoSpellCheckBackCompatComponent"/>
|
||||
<searchComponent name="rewriteFacetParameters" class="org.alfresco.solr.component.RewriteFacetParametersComponent" />
|
||||
<searchComponent name="rewriteFacetCounts" class="org.alfresco.solr.component.RewriteFacetCountsComponent" />
|
||||
<searchComponent name="setProcessedDenies" class="org.alfresco.solr.component.SetProcessedDeniesComponent" />
|
||||
|
||||
<transformer name="cached" class="org.alfresco.solr.transformer.CachedDocTransformerFactory" >
|
||||
</transformer>
|
||||
|
||||
|
||||
|
||||
</config>
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
#
|
||||
# solrcore.properties - used in solrconfig.xml
|
||||
#
|
||||
|
||||
enable.alfresco.tracking=false
|
||||
|
||||
#
|
||||
#These are replaced by the admin handler
|
||||
#
|
||||
#data.dir.root=DATA_DIR
|
||||
#data.dir.store=workspace/SpacesStore
|
||||
#alfresco.stores=workspace://SpacesStore
|
||||
|
||||
#
|
||||
# Properties loaded during alfresco tracking
|
||||
#
|
||||
|
||||
alfresco.host=localhost
|
||||
alfresco.port=8080
|
||||
alfresco.port.ssl=8443
|
||||
alfresco.baseUrl=/alfresco
|
||||
alfresco.cron=0/2 * * * * ? *
|
||||
|
||||
#alfresco.index.transformContent=false
|
||||
#alfresco.ignore.datatype.1=d:content
|
||||
alfresco.lag=1000
|
||||
alfresco.hole.retention=3600000
|
||||
# alfresco.hole.check.after is not used yet
|
||||
# It will reduce the hole checking load
|
||||
alfresco.hole.check.after=300000
|
||||
alfresco.batch.count=1000
|
||||
|
||||
# encryption
|
||||
|
||||
# none, https
|
||||
alfresco.secureComms=none
|
||||
|
||||
# ssl
|
||||
alfresco.encryption.ssl.keystore.type=JCEKS
|
||||
alfresco.encryption.ssl.keystore.provider=
|
||||
alfresco.encryption.ssl.keystore.location=ssl.repo.client.keystore
|
||||
alfresco.encryption.ssl.keystore.passwordFileLocation=ssl-keystore-passwords.properties
|
||||
alfresco.encryption.ssl.truststore.type=JCEKS
|
||||
alfresco.encryption.ssl.truststore.provider=
|
||||
alfresco.encryption.ssl.truststore.location=ssl.repo.client.truststore
|
||||
alfresco.encryption.ssl.truststore.passwordFileLocation=ssl-truststore-passwords.properties
|
||||
|
||||
# Tracking
|
||||
|
||||
alfresco.corePoolSize=1
|
||||
alfresco.maximumPoolSize=-1
|
||||
alfresco.keepAliveTime=120
|
||||
alfresco.threadPriority=5
|
||||
alfresco.threadDaemon=true
|
||||
alfresco.workQueueSize=-1
|
||||
|
||||
# HTTP Client
|
||||
|
||||
alfresco.maxTotalConnections=200
|
||||
alfresco.maxHostConnections=200
|
||||
alfresco.socketTimeout=360000
|
||||
|
||||
# SOLR caching
|
||||
|
||||
solr.filterCache.size=256
|
||||
solr.filterCache.initialSize=128
|
||||
solr.queryResultCache.size=1024
|
||||
solr.queryResultCache.initialSize=1024
|
||||
solr.documentCache.size=1024
|
||||
solr.documentCache.initialSize=1024
|
||||
solr.queryResultMaxDocsCached=2048
|
||||
|
||||
solr.authorityCache.size=128
|
||||
solr.authorityCache.initialSize=64
|
||||
solr.pathCache.size=256
|
||||
solr.pathCache.initialSize=128
|
||||
|
||||
solr.ownerCache.size=128
|
||||
solr.ownerCache.initialSize=64
|
||||
|
||||
solr.readerCache.size=128
|
||||
solr.readerCache.initialSize=64
|
||||
|
||||
solr.deniedCache.size=128
|
||||
solr.deniedCache.initialSize=64
|
||||
|
||||
# SOLR
|
||||
|
||||
solr.maxBooleanClauses=10000
|
||||
|
||||
# Batch fetch
|
||||
|
||||
alfresco.transactionDocsBatchSize=100
|
||||
alfresco.nodeBatchSize=10
|
||||
alfresco.changeSetAclsBatchSize=100
|
||||
alfresco.aclBatchSize=10
|
||||
alfresco.contentReadBatchSize=4000
|
||||
alfresco.contentUpdateBatchSize=1000
|
||||
|
||||
# Warming
|
||||
|
||||
solr.filterCache.autowarmCount=32
|
||||
solr.authorityCache.autowarmCount=4
|
||||
solr.pathCache.autowarmCount=32
|
||||
solr.deniedCache.autowarmCount=0
|
||||
solr.readerCache.autowarmCount=0
|
||||
solr.ownerCache.autowarmCount=0
|
||||
solr.queryResultCache.autowarmCount=4
|
||||
solr.documentCache.autowarmCount=512
|
||||
|
||||
solr.queryResultWindowSize=512
|
||||
|
||||
|
||||
#
|
||||
# TODO
|
||||
#
|
||||
# cross language support
|
||||
# locale expansion
|
||||
# logging check report ....
|
||||
#
|
||||
#
|
||||
|
||||
alfresco.commitInterval=1000
|
||||
alfresco.newSearcherInterval=2000
|
||||
|
||||
alfresco.doPermissionChecks=true
|
||||
|
||||
|
||||
#
|
||||
# Metadata pulling control
|
||||
#
|
||||
alfresco.metadata.skipDescendantDocsForSpecificTypes=false
|
||||
alfresco.metadata.ignore.datatype.0=cm:person
|
||||
alfresco.metadata.ignore.datatype.1=app:configurations
|
||||
alfresco.metadata.skipDescendantDocsForSpecificAspects=false
|
||||
#alfresco.metadata.ignore.aspect.0=
|
||||
|
||||
|
||||
#
|
||||
# Suggestions
|
||||
#
|
||||
solr.suggester.enabled=false
|
||||
# -1 to disable suggester build throttling
|
||||
solr.suggester.minSecsBetweenBuilds=3600
|
||||
|
||||
#
|
||||
# Limit the maximum text size of transformed content sent to the index - in bytes
|
||||
#
|
||||
alfresco.contentStreamLimit=10000000
|
||||
|
||||
#Sharding default values
|
||||
shard.instance=1
|
||||
shard.count=0
|
||||
shard.method=DB_ID
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
a
|
||||
an
|
||||
and
|
||||
are
|
||||
as
|
||||
at
|
||||
be
|
||||
but
|
||||
by
|
||||
for
|
||||
if
|
||||
in
|
||||
into
|
||||
is
|
||||
it
|
||||
no
|
||||
not
|
||||
of
|
||||
on
|
||||
or
|
||||
s
|
||||
such
|
||||
t
|
||||
that
|
||||
the
|
||||
their
|
||||
then
|
||||
there
|
||||
these
|
||||
they
|
||||
this
|
||||
to
|
||||
was
|
||||
will
|
||||
with
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user