Merge master into V2.0.x.

This commit is contained in:
Tom Page
2019-12-20 11:01:06 +00:00
112 changed files with 14837 additions and 2882 deletions
+1 -1
View File
@@ -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"),
@@ -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:
@@ -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
@@ -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:
@@ -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
View File
@@ -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
@@ -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);
}
}
}
@@ -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"));
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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");
}
}
@@ -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 &lt;&gt; 'testc.txt'"
expectedResults="7" />
<query
value="SELECT * FROM tas:document where cmis:name &lt; 'testc.txt'"
expectedResults="3" />
<query
value="SELECT * FROM tas:document where cmis:name &lt;= 'testc.txt'"
expectedResults="4" />
<query
value="SELECT * FROM tas:document where cmis:name &gt; 'testc.txt'"
expectedResults="4" />
<query
value="SELECT * FROM tas:document where cmis:name &gt;= '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 &lt;&gt; 'testf'"
expectedResults="8" />
<query
value="SELECT * FROM tas:folder where cmis:name &lt; 'testf'"
expectedResults="3" />
<query
value="SELECT * FROM tas:folder where cmis:name &lt;= 'testf'"
expectedResults="4" />
<query
value="SELECT * FROM tas:folder where cmis:name &gt; 'testf'"
expectedResults="5" />
<query
value="SELECT * FROM tas:folder where cmis:name &gt;= '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 &lt;&gt; '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 &gt;= '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 &lt;&gt; '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 &gt;= '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 &lt;&gt; '223'"
expectedResults="7" />
<query
value="SELECT * FROM tas:document where tas:IntPropertyC &lt; '223'"
expectedResults="1" />
<query
value="SELECT * FROM tas:document where tas:IntPropertyC &lt;= '224'"
expectedResults="3" />
<query
value="SELECT * FROM tas:document where tas:IntPropertyC &gt;= '224'"
expectedResults="6" />
<query
value="SELECT * FROM tas:document where tas:IntPropertyC &gt; '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 &lt;&gt; '223'"
expectedResults="8" />
<query
value="SELECT * FROM tas:folder where tas:IntPropertyF &lt; '223'"
expectedResults="1" />
<query
value="SELECT * FROM tas:folder where tas:IntPropertyF &lt;= '224'"
expectedResults="3" />
<query
value="SELECT * FROM tas:folder where tas:IntPropertyF &gt;= '224'"
expectedResults="7" />
<query
value="SELECT * FROM tas:folder where tas:IntPropertyF &gt; '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>
+1 -1
View File
@@ -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 -->
+16
View File
@@ -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|
+92 -2
View File
@@ -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>
@@ -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)
{
}
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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;
}
@@ -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();
}
}
@@ -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();
}
@@ -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();
}
}
@@ -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();
}
@@ -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);
}
}
}
@@ -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);
@@ -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.");
}
}
}
@@ -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);
@@ -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;
@@ -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());
}
}
@@ -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";
}
@@ -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);
@@ -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;
@@ -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);
@@ -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;
}
}
@@ -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)
@@ -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);
@@ -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);
}
}
}
@@ -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
@@ -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);
}
}
@@ -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()));
}
}
@@ -56,7 +56,7 @@ public class AdminHandlerDistributedIT extends AbstractAlfrescoDistributedIT
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
});
@@ -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;
}
/**
@@ -63,7 +63,7 @@ public class CoresCreateUpdateDistributedIT extends AbstractAlfrescoDistributedI
}
@After
private void destroyData() throws Throwable
private void destroyData()
{
dismissSolrServers();
}
@@ -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,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -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()
{
}
}
}
}
@@ -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";
}
@@ -52,7 +52,7 @@ public class TemplatesDistributedIT extends AbstractAlfrescoDistributedIT
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -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"));
}
}
@@ -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
}
}
}
@@ -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
@@ -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]);
}
}
@@ -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());
}
}
@@ -46,7 +46,7 @@ public class AlfrescoHighligherDistributedIT extends AbstractAlfrescoDistributed
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -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);
}
}
@@ -48,7 +48,7 @@ import static org.hamcrest.core.Is.is;
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -42,7 +42,7 @@ public class DistributedAlfrescoSolrSpellcheckerIT extends AbstractAlfrescoDistr
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -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();
}
}
@@ -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);
}
}
@@ -67,7 +67,7 @@ public class DistributedAclIdAlfrescoSolrTrackerIT extends AbstractAlfrescoDistr
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -59,7 +59,7 @@ public class DistributedAlfrescoSolrJsonIT extends AbstractAlfrescoDistributedIT
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -66,7 +66,7 @@ public class DistributedAlfrescoSolrTrackerIT extends AbstractAlfrescoDistribute
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -64,7 +64,7 @@ public class DistributedAlfrescoSolrTrackerRaceIT extends AbstractAlfrescoDistri
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -43,7 +43,7 @@ public class DistributedDateQuarterAlfrescoSolrTrackerIT extends DistributedDate
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -42,7 +42,7 @@ public class DistributedDateSplitYearAlfrescoSolrTrackerIT extends DistributedDa
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -62,7 +62,7 @@ public class DistributedDbidRangeAlfrescoSolrTrackerIT extends AbstractAlfrescoD
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -65,7 +65,7 @@ public class DistributedExpandDbidRangeAlfrescoSolrTrackerIT extends AbstractAlf
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -66,7 +66,7 @@ public class DistributedExplicitShardIdWithStaticPropertyRouterIT extends Abstra
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -71,7 +71,7 @@ public class DistributedExplicitShardRoutingTrackerIT extends AbstractAlfrescoDi
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -82,7 +82,7 @@ public class DistributedPropertyBasedAlfrescoSolrTrackerIT extends AbstractAlfre
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -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
@@ -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).
@@ -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>
@@ -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>
@@ -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&lt;-1 5&lt;-2 6&lt;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\&quot;&apos;]{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">.,!? &#9;&#10;&#13;</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>
@@ -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
@@ -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
@@ -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).
@@ -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>
@@ -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>
@@ -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&lt;-1 5&lt;-2 6&lt;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\&quot;&apos;]{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">.,!? &#9;&#10;&#13;</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>
@@ -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
@@ -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