Merge branch 'master' of github.com:Alfresco/SearchServices into fix/SEARCH-1490_cascading_not_work_in_sharding_environment

This commit is contained in:
eliaporciani
2019-04-25 17:12:36 +02:00
8 changed files with 591 additions and 1061 deletions
@@ -0,0 +1,46 @@
# 3. Combined Codebase
Date: 09/04/2019
## Status
Approved
## Context
Historically Alfresco has had a bad experience of having a monolithic codebase in SVN. The main issue with this was the
inability to easily work on feature branches, resulting in frequent conflicting changes. To make matters worse this was
seen as hard to move to git, since GitHub has/had a maximum limit on repository size and there were several large
binary artifacts stored in the Alfresco history [[1]].
More recently the Alfresco codebase has been split into a large number of small git repositories [[2]].
This proposal is to merge the various git repositories together. By doing this we aim to:
1. Remove the effort of creating releases of library projects.
1. Allow tests to be written on the same branch (in the same repository) as the production code (i.e. facilitate TDD).
1. Make it easier for everyone (especially people outside the team and new starters) to find the 'right bit' of code.
[1]: https://community.alfresco.com/community/ecm/blog/2015/04/01/so-when-is-alfresco-moving-to-github
[2]: https://ts.alfresco.com/share/proxy/alfresco/api/node/content/versionStore/version2Store/a0c2492f-6354-4b98-adfc-e63d5c2209f5/SearchCodeBase.png
## Decision
We will merge the search-related repositories together and preserve their history. We will not attempt to merge code
that other teams also need (for example the TAS test utilities or the alfresco-data-model projects).
## Consequences
An epic has been raised containing the next steps [[3]]. Broadly speaking these are:
1. Merge the alfresco-solr-client library with the SearchServices repository.
1. Merge the insight-engine, insight-jdbc and insight-zeppelin repositories.
1. A consequence of this is that future releases of these artifacts will share version numbers.
1. Put all the end-to-end tests in a single testing project.
1. Merge the (public) SearchServices code into the (private) InsightEngine repository and set up a mirror so the
community code is still publicly editable.
1. Merge the tests with the production code.
1. Merge the build scripts with the production code.
[3]: https://issues.alfresco.com/jira/browse/SEARCH-1393
@@ -0,0 +1,44 @@
# 4. Community Mirror
Date: 09/04/2019
## Status
Approved
## Context
In [ADR 3: "Combined Codebase"](0003-combined-codebase.md) we decided to merge the Search Services and Insight Engine
repositories. Since we want to enable the community to submit pull requests to the Search Services project we need a
way to keep this code up to date on GitHub.
## Decision
We will mirror `master` and all branches starting with `release/` to a branch with the same name on GitHub. We will
exclude the alfresco-insight-engine-parent directory. We will include these commands as part of our build to do this:
```
# This avoids making changes to the original branch.
get checkout -b tempBranch
# This strips all enterprise changes (in a reproducible way) and pushes any updates to the mirror.
git filter-branch -f --prune-empty --index-filter 'git rm -r --cached --ignore-unmatch alfresco-insight-engine-parent'
git push out HEAD:$branch
# This resets us back to where we were before the filtering.
git checkout $branch
```
## Consequences
A ticket has been raised to track this [[1]]. A script has been written to do the initial codebase merge and
mirroring [[2]].
We will rewrite the whole history of the SearchServices repository using the mirroring command. This will result in the
new repository containing a new root pom file and the search services code beneath that.
All changes except those within the module called insight-engine will be mirrored.
The root pom file in the community project will include a reference to the insight-engine pom file, so community users
will have to build from within the alfresco-search-parent directory.
[1]: https://issues.alfresco.com/jira/browse/SEARCH-1397
[2]: https://git.alfresco.com/search_discovery/combinerScript/blob/master/combineSearch.sh
@@ -0,0 +1,25 @@
# 5. Merge Tests And Production Code
Date: 09/04/2019
## Status
Approved
## Context
In [ADR 3: "Combined Codebase"](0003-combined-codebase.md) we decided to merge the production and end-to-end test
repositories. In [ADR 4: "Community Mirror"](0004-community-mirror.md) we discuss setting up a mirror for the community
code.
## Decision
We will separate the end-to-end test code in half so that any code solely related to Insight Engine won't be mirrored.
We will remove the existing test groups for the different versions of Search Services and Insight Engine, and instead
delete any tests from branches where they should not be run.
## Consequences
It will be possible to include production code changes along with all required test changes in the same merge request.
It will be easy to get new tests running before production code is written without causing other branches to fail.
@@ -0,0 +1,47 @@
# 6. Release Synchronisation
Date: 24/04/2019
## Status
Approved
## Context
In [ADR 3: "Combined Codebase"](0003-combined-codebase.md) we decided to merge the Search Services and Insight Engine
repositories. In [ADR 4: "Community Mirror"](0004-community-mirror.md) we discussed how we would set up a build job to
ensure community code is available to the community. In particular we decided:
> We will mirror `master` and all branches starting with `release/` to a branch with the same name on GitHub.
## Decision
During the combining of the Search Services and Insight Engine codebases we will create a branch `master` by merging
the existing `master` branches of those two projects. We will update the version of Insight Engine to match Search Services - that
is 1.4.0-SNAPSHOT.
## Consequences
The next minor release of Insight Engine will be 1.4.0 which will happen at the same time as the release of Search
Services 1.4.0. There will be no Insight Engine releases with the versions 1.2.x or 1.3.x.
Here are a few examples to illustrate the process for future releases. Note that other releases (including service
packs, etc.) will follow a similar logic based on whether they are pre- or post- 1.4.0.
* SS 1.3.0 needs a hotfix
We release from the `release/alfresco-search-services/V1.3.0.x` branch.
* IE 1.1.0 needs a hotfix
We release from the `release/alfresco-insight-engine/V1.1.0.x` branch. We need to use the maven version 1.1.0.2 for the
IE modules and 1.4.0-IE1.1.0.2 for the SS modules.
* SS 1.4.0 needs a hotfix
We'll create a hotfix release for both SS and IE (since they will both have the same issue). This will be from the
`release/V1.4.0.x` branch (which would have already been created if SS/IE 1.4.0 had been released).
* IE 1.4.0 needs a hotfix
We'll create a hotfix release for both SS and IE from `release/V1.4.0.x` (and not publicise SS 1.4.0.1).
@@ -145,6 +145,7 @@ import org.apache.lucene.util.BytesRefBuilder;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.ModifiableSolrParams;
@@ -2354,6 +2355,10 @@ public class SolrInformationServer implements InformationServer
if (cachedDoc != null)
{
ofNullable(cachedDoc.getField("MINHASH"))
.map(SolrInputField::getValue)
.ifPresent(minHash -> newDoc.setField("MINHASH", minHash));
// Builds up the new solr doc from the cached content regardless of whether or not it is current
List<FieldInstance> fields = AlfrescoSolrDataModel.getInstance().getIndexedFieldNamesForProperty(
propertyQName).getFields();
@@ -19,50 +19,75 @@
package org.alfresco.solr.query;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.client.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.StringPropertyValue;
import org.alfresco.solr.client.Transaction;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.LegacyNumericRangeQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static java.util.stream.IntStream.range;
import static org.alfresco.solr.AlfrescoSolrUtils.*;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AlfrescoSolrFingerprintTest extends AbstractAlfrescoSolrTests
{
private static Log logger = LogFactory.getLog(AlfrescoSolrFingerprintTest.class);
private static long MAX_WAIT_TIME = 80000;
@BeforeClass
public static void beforeClass() throws Exception
{
initAlfrescoCore("schema-fingerprint.xml");
initAlfrescoCore("schema.xml");
}
private Acl acl;
@Before
public void setUp() throws Exception {
// if you override setUp or tearDown, you better callf
// the super classes version
//clearIndex();
//assertU(commit());
public void prepare() throws Exception
{
AclChangeSet aclChangeSet = getAclChangeSet(1);
acl = getAcl(aclChangeSet);
Acl acl2 = getAcl(aclChangeSet);
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, singletonList("joel"), singletonList("phil"), null);
AclReaders aclReaders2 = getAclReaders(aclChangeSet, acl2, singletonList("jim"), singletonList("phil"), null);
indexAclChangeSet(aclChangeSet,
asList(acl, acl2),
asList(aclReaders, aclReaders2));
// Check for the ACL state stamp.
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!ACLTX")), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_ACLTXID, aclChangeSet.getId(), aclChangeSet.getId() + 1, true, false), BooleanClause.Occur.MUST));
BooleanQuery waitForQuery = builder.build();
waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME);
}
@After
public void clearQueue() throws Exception {
public void clearQueue()
{
SOLRAPIQueueClient.nodeMetaDataMap.clear();
SOLRAPIQueueClient.transactionQueue.clear();
SOLRAPIQueueClient.aclChangeSetQueue.clear();
@@ -70,234 +95,255 @@ public class AlfrescoSolrFingerprintTest extends AbstractAlfrescoSolrTests
SOLRAPIQueueClient.aclMap.clear();
SOLRAPIQueueClient.nodeMap.clear();
SOLRAPIQueueClient.nodeContentMap.clear();
clearIndex();
assertU(commit());
}
@Test
public void testBasciFingerPrint() throws Exception
private void makeSureTransactionHasBeenIndexed(long transactionId) throws Exception
{
/*
* Create and index an AclChangeSet.
*/
logger.info("######### Starting fingerprint test ###########");
AclChangeSet aclChangeSet = getAclChangeSet(1);
Acl acl = getAcl(aclChangeSet);
Acl acl2 = getAcl(aclChangeSet);
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, list("joel"), list("phil"), null);
AclReaders aclReaders2 = getAclReaders(aclChangeSet, acl2, list("jim"), list("phil"), null);
indexAclChangeSet(aclChangeSet,
list(acl, acl2),
list(aclReaders, aclReaders2));
//Check for the ACL state stamp.
//Check for the TXN state stamp.
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!ACLTX")), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_ACLTXID, aclChangeSet.getId(), aclChangeSet.getId() + 1, true, false), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!TX")), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_TXID, transactionId, transactionId + 1, true, false), BooleanClause.Occur.MUST));
BooleanQuery waitForQuery = builder.build();
waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME);
}
logger.info("#################### Passed First Test ##############################");
/*
* Create and index a Transaction
*/
//First create a transaction.
@Test
public void testBasicFingerprint() throws Exception
{
Transaction txn = getTransaction(0, 4);
//Next create two nodes to update for the transaction
Node node1 = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
Node node2 = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
Node node3 = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
Node node4 = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
//Next create the NodeMetaData for each node. TODO: Add more metadata
NodeMetaData nodeMetaData1 = getNodeMetaData(node1, txn, acl, "mike", null, false);
NodeMetaData nodeMetaData2 = getNodeMetaData(node2, txn, acl, "mike", null, false);
NodeMetaData nodeMetaData3 = getNodeMetaData(node3, txn, acl, "mike", null, false);
NodeMetaData nodeMetaData4 = getNodeMetaData(node4, txn, acl, "mike", null, false);
List<String> content = new ArrayList();
Random randomizer = new Random(1);
String aFirstToken = Integer.toString(Math.abs(randomizer.nextInt()));
indexTransaction(txn,
asList(node1, node2, node3, node4),
asList(nodeMetaData1, nodeMetaData2, nodeMetaData3, nodeMetaData4),
randomTextContent());
makeSureTransactionHasBeenIndexed(txn.getId());
makeSureContentNodesHaveBeenIndexed("mike", aFirstToken, 4);
assertFingerprintQueryCorrectness(node1.getId(),
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
assertFingerprintQueryCorrectness(nodeMetaData1.getNodeRef().getId(),
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
assertFingerprintQueryCorrectness(node1.getId() + "_70",
"*[count(//doc)= 2]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']");
assertFingerprintQueryCorrectness(nodeMetaData1.getNodeRef().getId() + "_70",
"*[count(//doc)= 2]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']");
assertFingerprintQueryCorrectness(node1.getId() + "_45",
"*[count(//doc)= 3]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']");
assertFingerprintQueryCorrectness(nodeMetaData1.getNodeRef().getId() + "_45",
"*[count(//doc)= 3]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']");
assertFingerprintQueryCorrectness(node4.getId() + "_30",
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
assertFingerprintQueryCorrectness(nodeMetaData4.getNodeRef().getId() + "_30",
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
assertFingerprintQueryCorrectness(node4.getId(),
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
assertFingerprintQueryCorrectness(nodeMetaData4.getNodeRef().getId(),
"*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
}
@Test
public void testFingerprintStillExistsAfterNodeMetadataUpdate() throws Exception
{
Transaction txn = getTransaction(0, 1);
Node fileNode = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
NodeMetaData fileMetaData = getNodeMetaData(fileNode, txn, acl, "mike", null, false);
indexTransaction(
txn,
singletonList(fileNode),
singletonList(fileMetaData),
singletonList("This is a text content which is longer than the default hello world " + fileNode.getId() +
" returned by the Mock SOLRAPIQueueClient. This is needed because the \"min_hash\" field type " +
"definition in Solr doesn't take in account fields which produce less than 5 tokens (see the " +
"ShingleFilter settings)."));
makeSureTransactionHasBeenIndexed(txn.getId());
makeSureContentNodeHasBeenIndexed(fileNode, "mike", "world");
assertFingerprintQueryCorrectness(fileNode.getId(), "*[count(//doc)=1]","//result/doc[1]/long[@name='DBID'][.='" + fileNode.getId() + "']");
// Let's update the test node
fileMetaData.setOwner("Andrea");
fileMetaData.getProperties().put(ContentModel.PROP_TITLE, new StringPropertyValue("This is the new file \"title\" metadata attribute."));
reindexTransactionId(txn.getId());
makeSureContentNodeHasBeenIndexed(fileNode, "Andrea", "world");
assertFingerprintQueryCorrectness(fileNode.getId(), "*[count(//doc)=1]","//result/doc[1]/long[@name='DBID'][.='" + fileNode.getId() + "']");
}
/**
* Queries the index using a token from the (dummy) text produced by the test framework ("world", actually).
* Once the query returns a positive result we are sure the ContentTracker
*
* <ol>
* <li>
* Fetched the text content associated with the current node, from Alfresco
* </li>
* <li>
* Computed a fingerprint (using the retrieved text) for the node
* </li>
* <li>
* Updated the node definition in the (Solr)ContentStore and in Solr
* </li>
* </ol>
*
* Last but not least, we are also making sure that CommitTracker executed its cycle as well (otherwise document
* wouldn't be searchable).
*
* @param node an addition term which will be appended as a required clause in the executed query.
* @param testTerm a term which is supposed to be in the indexed content
* @param owner the #FIELD_OWNER which will be used as an additional required query clause.
* @throws Exception in case the MAX_WAIT_TIME is reached and the node is not in results.
*/
private void makeSureContentNodeHasBeenIndexed(final Node node, final String owner, String testTerm) throws Exception
{
waitForDocCount(new TermQuery(new Term(QueryConstants.FIELD_READER, "jim")), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", Long.toString(node.getId()))), 1, MAX_WAIT_TIME);
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_OWNER, owner)), BooleanClause.Occur.MUST));
waitForDocCount(builder.build(), 1, MAX_WAIT_TIME);
}
/**
* Queries the index using a token from the (dummy) text produced by the test framework.
* Once the query returns a positive result we are sure the ContentTracker
*
* <ol>
* <li>
* Fetched the text content associated with the test nodes, from Alfresco
* </li>
* <li>
* Computed a fingerprint (using the retrieved text) for each node
* </li>
* <li>
* Updated the nodes definitions in the (Solr)ContentStore and in Solr
* </li>
* </ol>
*
* Last but not least, we are also making sure that CommitTracker executed its cycle as well (otherwise documents
* wouldn't be searchable).
*
* @param owner the #FIELD_OWNER which will be used as an additional required query clause.
* @param testTerm a term which is supposed to be in the indexed content
* @throws Exception in case the MAX_WAIT_TIME is reached and the node is not in results.
*/
private void makeSureContentNodesHaveBeenIndexed(final String owner, String testTerm, final int expectedCount) throws Exception
{
waitForDocCount(new TermQuery(new Term(QueryConstants.FIELD_READER, "jim")), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), expectedCount, MAX_WAIT_TIME);
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_OWNER, owner)), BooleanClause.Occur.MUST));
waitForDocCount(builder.build(), expectedCount, MAX_WAIT_TIME);
}
/**
* Asserts that a query in the format <pre>FINGERPRINT:<DBID></pre> correctly returns the node we are testing.
*
* @param id the node identifier.
*/
private void assertFingerprintQueryCorrectness(long id, String ... assertions)
{
assertFingerprintQueryCorrectness(String.valueOf(id), assertions);
}
/**
* Asserts that a query in the format <pre>FINGERPRINT:<DBID></pre> correctly returns the node we are testing.
*
* @param id the node identifier.
*/
private void assertFingerprintQueryCorrectness(String id, String ... assertions)
{
ModifiableSolrParams params = new ModifiableSolrParams()
.add("q", "FINGERPRINT:" + id)
.add("qt", "/afts")
.add("start", "0")
.add("rows", "6")
.add("sort", "id asc");
// .add("fq", "{!afts}AUTHORITY_FILTER_FROM_JSON");
SolrServletRequest req = areq(params, "{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [ \"joel\"], \"tenants\": [ \"\" ]}");
assertQ(req, assertions);
}
private List<String> randomTextContent()
{
int[] sizes = {2000, 1000, 1500, 750};
Random r = new Random(1);
String token1 = Integer.toString(Math.abs(r.nextInt()));
for(int i=0; i<4; i++) {
Random rand = new Random(1);
StringBuilder buf = new StringBuilder();
int size = sizes[i];
for(int s=0; s<size; s++) {
if(s>0) {
buf.append(" ");
}
buf.append(Integer.toString(Math.abs(rand.nextInt())));
}
content.add(buf.toString());
}
//Index the transaction, nodes, and nodeMetaDatas.
//Note that the content is automatically created by the test framework.
indexTransaction(txn,
list(node1, node2, node3, node4),
list(nodeMetaData1, nodeMetaData2, nodeMetaData3, nodeMetaData4),
content);
//Check for the TXN state stamp.
logger.info("#################### Started Second Test ##############################");
builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!TX")), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_TXID, txn.getId(), txn.getId() + 1, true, false), BooleanClause.Occur.MUST));
waitForQuery = builder.build();
waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME);
logger.info("#################### Passed Second Test ##############################");
/*
* Query the index for the content
*/
waitForDocCount(new TermQuery(new Term(QueryConstants.FIELD_READER, "jim")), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", token1)), 4, MAX_WAIT_TIME);
logger.info("#################### Passed Third Test ##############################");
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + node1.getId()); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl", "DBID,score");
params.add("start", "0");
params.add("rows", "6");
SolrServletRequest req = areq(params, null);
assertQ(req, "*[count(//doc)=4]",
"//result/doc[1]/long[@name='DBID'][.='" + node1.getId() + "']",
"//result/doc[2]/long[@name='DBID'][.='" + node3.getId() + "']",
"//result/doc[3]/long[@name='DBID'][.='" + node2.getId() + "']",
"//result/doc[4]/long[@name='DBID'][.='" + node4.getId() + "']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + node1.getId() + "_70"); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 2]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + node1.getId()+"_45"); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 3]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node2.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + node1.getId()+"_30");
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + node4.getId());
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node4.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node1.getId()+"']");
//Test nodeRef
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + nodeMetaData1.getNodeRef().getId()); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl", "DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)=4]",
"//result/doc[1]/long[@name='DBID'][.='" + node1.getId() + "']",
"//result/doc[2]/long[@name='DBID'][.='" + node3.getId() + "']",
"//result/doc[3]/long[@name='DBID'][.='" + node2.getId() + "']",
"//result/doc[4]/long[@name='DBID'][.='" + node4.getId() + "']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + nodeMetaData1.getNodeRef().getId() + "_70"); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 2]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + nodeMetaData1.getNodeRef().getId()+"_45"); //Query for an id in the content field. The node id is automatically populated into the content field by test framework
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 3]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node2.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + nodeMetaData1.getNodeRef().getId()+"_30");
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node1.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node4.getId()+"']");
params = new ModifiableSolrParams();
params.add("q", "FINGERPRINT:" + nodeMetaData4.getNodeRef().getId());
params.add("qt", "/afts");
params.add("fl","DBID,score");
params.add("start", "0");
params.add("rows", "6");
req = areq(params, null);
assertQ(req, "*[count(//doc)= 4]",
"//result/doc[1]/long[@name='DBID'][.='"+node4.getId()+"']",
"//result/doc[2]/long[@name='DBID'][.='"+node2.getId()+"']",
"//result/doc[3]/long[@name='DBID'][.='"+node3.getId()+"']",
"//result/doc[4]/long[@name='DBID'][.='"+node1.getId()+"']");
return stream(sizes)
.mapToObj(item -> {
Random randomizer = new Random(1);
return range(0, item)
.mapToObj(i -> randomizer.nextInt())
.map(Object::toString)
.collect(Collectors.joining(" "));})
.collect(Collectors.toList());
}
}
@@ -18,6 +18,7 @@
*/
package org.alfresco.solr.query;
import static java.util.Arrays.asList;
import static org.alfresco.solr.AlfrescoSolrUtils.getAcl;
import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet;
import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders;
@@ -25,12 +26,13 @@ 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;
import static java.util.Collections.singletonList;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.client.Acl;
@@ -38,6 +40,7 @@ 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.StringPropertyValue;
import org.alfresco.solr.client.Transaction;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
@@ -60,31 +63,26 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDistributedTest
{
private static Node[] nodes = new Node[4];
private static NodeMetaData[] nodesMetada = new NodeMetaData[4];
private static long MAX_WAIT_TIME = 80000;
private static Node[] NODES = new Node[4];
private static NodeMetaData[] NODES_METADATA = new NodeMetaData[4];
private static Acl ACL;
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(2,"DistributedAlfrescoSolrFingerPrintTest",null);
/*
* Create and index an AclChangeSet.
*/
initSolrServers(2,getClassName(),null);
AclChangeSet aclChangeSet = getAclChangeSet(1);
Acl acl = getAcl(aclChangeSet);
ACL = getAcl(aclChangeSet);
Acl acl2 = getAcl(aclChangeSet);
AclReaders aclReaders = getAclReaders(aclChangeSet, ACL, singletonList("joel"), singletonList("phil"), null);
AclReaders aclReaders2 = getAclReaders(aclChangeSet, acl2, singletonList("jim"), singletonList("phil"), null);
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, list("joel"), list("phil"), null);
AclReaders aclReaders2 = getAclReaders(aclChangeSet, acl2, list("jim"), list("phil"), null);
indexAclChangeSet(aclChangeSet,
list(acl, acl2),
list(aclReaders, aclReaders2));
indexAclChangeSet(aclChangeSet, asList(ACL, acl2), asList(aclReaders, aclReaders2));
//Check for the ACL state stamp.
BooleanQuery.Builder builder = new BooleanQuery.Builder();
@@ -93,27 +91,22 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
BooleanQuery waitForQuery = builder.build();
waitForDocCountAllCores(waitForQuery, 1, 80000);
/*
* Create and index a Transaction
*/
//First create a transaction.
Transaction txn = getTransaction(0, 4);
//Next create two nodes to update for the transaction
nodes[0] = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
nodes[1] = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
nodes[2] = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
nodes[3] = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
//Next create two NODES to update for the transaction
NODES[0] = getNode(txn, ACL, Node.SolrApiNodeStatus.UPDATED);
NODES[1] = getNode(txn, ACL, Node.SolrApiNodeStatus.UPDATED);
NODES[2] = getNode(txn, ACL, Node.SolrApiNodeStatus.UPDATED);
NODES[3] = getNode(txn, ACL, Node.SolrApiNodeStatus.UPDATED);
//Next create the NodeMetaData for each node. TODO: Add more metadata
nodesMetada[0] = getNodeMetaData(nodes[0], txn, acl, "mike", null, false);
nodesMetada[1] = getNodeMetaData(nodes[1], txn, acl, "mike", null, false);
nodesMetada[2] = getNodeMetaData(nodes[2], txn, acl, "mike", null, false);
nodesMetada[3] = getNodeMetaData(nodes[3], txn, acl, "mike", null, false);
NODES_METADATA[0] = getNodeMetaData(NODES[0], txn, ACL, "mike", null, false);
NODES_METADATA[1] = getNodeMetaData(NODES[1], txn, ACL, "mike", null, false);
NODES_METADATA[2] = getNodeMetaData(NODES[2], txn, ACL, "mike", null, false);
NODES_METADATA[3] = getNodeMetaData(NODES[3], txn, ACL, "mike", null, false);
List<String> content = new ArrayList();
List<String> content = new ArrayList<>();
int[] sizes = {2000, 1000, 1500, 750};
Random r = new Random(1);
@@ -127,16 +120,16 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
if(s>0) {
buf.append(" ");
}
buf.append(Integer.toString(Math.abs(rand.nextInt())));
buf.append(Math.abs(rand.nextInt()));
}
content.add(buf.toString());
}
//Index the transaction, nodes, and nodeMetaDatas.
//Index the transaction, NODES, and nodeMetaDatas.
//Note that the content is automatically created by the test framework.
indexTransaction(txn,
list(nodes[0], nodes[1], nodes[2], nodes[3]),
list(nodesMetada[0], nodesMetada[1], nodesMetada[2], nodesMetada[3]),
asList(NODES[0], NODES[1], NODES[2], NODES[3]),
asList(NODES_METADATA[0], NODES_METADATA[1], NODES_METADATA[2], NODES_METADATA[3]),
content);
//Check for the TXN state stamp.
@@ -156,7 +149,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
}
@AfterClass
private static void destroyData() throws Throwable
private static void destroyData()
{
dismissSolrServers();
}
@@ -167,7 +160,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:"+nodes[0].getId(),
params("q", "FINGERPRINT:"+ NODES[0].getId(),
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -175,22 +168,22 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 4);
assertEquals(4, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
SolrDocument doc2 = docs.get(2);
long dbid2 = (long)doc2.getFieldValue("DBID");
assertTrue(dbid2 == nodes[1].getId());
assertEquals(dbid2, NODES[1].getId());
SolrDocument doc3 = docs.get(3);
long dbid3 = (long)doc3.getFieldValue("DBID");
assertTrue(dbid3 == nodes[3].getId());
assertEquals(dbid3, NODES[3].getId());
}
@Test
@@ -199,7 +192,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + nodes[0].getId()+"_70",
params("q", "FINGERPRINT:" + NODES[0].getId()+"_70",
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -207,14 +200,14 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 2);
assertEquals(2, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
}
@Test
@@ -223,7 +216,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + nodes[0].getId()+"_45",
params("q", "FINGERPRINT:" + NODES[0].getId()+"_45",
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -231,18 +224,18 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 3);
assertEquals(3, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
SolrDocument doc2 = docs.get(2);
long dbid2 = (long)doc2.getFieldValue("DBID");
assertTrue(dbid2 == nodes[1].getId());
assertEquals(dbid2, NODES[1].getId());
}
@Test
@@ -251,7 +244,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:"+nodesMetada[0].getNodeRef().getId(),
params("q", "FINGERPRINT:"+ NODES_METADATA[0].getNodeRef().getId(),
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -259,22 +252,22 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 4);
assertEquals(4, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
SolrDocument doc2 = docs.get(2);
long dbid2 = (long)doc2.getFieldValue("DBID");
assertTrue(dbid2 == nodes[1].getId());
assertEquals(dbid2, NODES[1].getId());
SolrDocument doc3 = docs.get(3);
long dbid3 = (long)doc3.getFieldValue("DBID");
assertTrue(dbid3 == nodes[3].getId());
assertEquals(dbid3, NODES[3].getId());
}
@Test
@@ -283,7 +276,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + nodesMetada[0].getNodeRef().getId() +"_70",
params("q", "FINGERPRINT:" + NODES_METADATA[0].getNodeRef().getId() +"_70",
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -291,14 +284,14 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 2);
assertEquals(2, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
}
@Test
@@ -307,7 +300,7 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
putHandleDefaults();
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + nodesMetada[0].getNodeRef().getId() +"_45",
params("q", "FINGERPRINT:" + NODES_METADATA[0].getNodeRef().getId() +"_45",
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
@@ -315,20 +308,113 @@ public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDist
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertTrue(docs.getNumFound() == 3);
assertEquals(3, docs.getNumFound());
SolrDocument doc0 = docs.get(0);
long dbid0 = (long)doc0.getFieldValue("DBID");
assertTrue(dbid0 == nodes[0].getId());
assertEquals(dbid0, NODES[0].getId());
SolrDocument doc1 = docs.get(1);
long dbid1 = (long)doc1.getFieldValue("DBID");
assertTrue(dbid1 == nodes[2].getId());
assertEquals(dbid1, NODES[2].getId());
SolrDocument doc2 = docs.get(2);
long dbid2 = (long)doc2.getFieldValue("DBID");
assertTrue(dbid2 == nodes[1].getId());
assertEquals(dbid2, NODES[1].getId());
}
}
@Test
public void testFingerprintStillExistsAfterNodeMetadataUpdate() throws Exception
{
putHandleDefaults();
Transaction txn = getTransaction(0, 1);
Node fileNode = getNode(txn, ACL, Node.SolrApiNodeStatus.UPDATED);
NodeMetaData fileMetaData = getNodeMetaData(fileNode, txn, ACL, "mike", null, false);
indexTransaction(
txn,
singletonList(fileNode),
singletonList(fileMetaData),
singletonList("This is a text content which is longer than the default hello world " + fileNode.getId() +
" returned by the Mock SOLRAPIQueueClient. This is needed because the \"min_hash\" field type " +
"definition in Solr doesn't take in account fields which produce less than 5 tokens (see the " +
"ShingleFilter settings)."));
makeSureContentNodeHasBeenIndexed(fileNode, "mike", "longer");
QueryResponse response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + fileMetaData.getNodeRef().getId(),
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
"fl", "DBID,score",
"rows", "100"));
SolrDocumentList docs = response.getResults();
assertEquals(1, docs.getNumFound());
assertEquals(fileNode.getId(), docs.iterator().next().getFieldValue("DBID"));
// Let's update the test node
fileMetaData.setOwner("Andrea");
fileMetaData.getProperties().put(ContentModel.PROP_TITLE, new StringPropertyValue("This is the new file \"title\" metadata attribute."));
txn = getTransaction(0, 1);
indexTransaction(
txn,
singletonList(fileNode),
singletonList(fileMetaData));
makeSureContentNodeHasBeenIndexed(fileNode, "Andrea", "longer");
response = query(getDefaultTestClient(), true,
"{\"locales\":[\"en\"], \"templates\": [{\"name\":\"t1\", \"template\":\"%cm:content\"}], \"authorities\": [\"joel\"], \"tenants\": []}",
params("q", "FINGERPRINT:" + fileMetaData.getNodeRef().getId(),
"qt", "/afts",
"shards.qt", "/afts",
"start", "0",
"fl", "DBID,score",
"rows", "100"));
docs = response.getResults();
assertEquals(1, docs.getNumFound());
assertEquals(fileNode.getId(), docs.iterator().next().getFieldValue("DBID"));
}
/**
* Queries the index using a token from the (dummy) text produced by the test framework ("world", actually).
* Once the query returns a positive result we are sure the ContentTracker
*
* <ol>
* <li>
* Fetched the text content associated with the current node, from Alfresco
* </li>
* <li>
* Computed a fingerprint (using the retrieved text) for the node
* </li>
* <li>
* Updated the node definition in the (Solr)ContentStore and in Solr
* </li>
* </ol>
*
* Last but not least, we are also making sure that CommitTracker executed its cycle as well (otherwise document
* wouldn't be searchable).
*
* @param node an addition term which will be appended as a required clause in the executed query.
* @param testTerm a term which is supposed to be in the indexed content
* @param owner the #FIELD_OWNER which will be used as an additional required query clause.
* @throws Exception in case the MAX_WAIT_TIME is reached and the node is not in results.
*/
private void makeSureContentNodeHasBeenIndexed(final Node node, final String owner, String testTerm) throws Exception
{
waitForDocCount(new TermQuery(new Term(QueryConstants.FIELD_READER, "jim")), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), 1, MAX_WAIT_TIME);
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", Long.toString(node.getId()))), 1, MAX_WAIT_TIME);
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", testTerm)), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_OWNER, owner)), BooleanClause.Occur.MUST));
waitForDocCount(builder.build(), 1, MAX_WAIT_TIME);
}
}
@@ -1,769 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
TODO
- Geospatial ?
- Currency ?
- (default facetting configuration)
-->
<schema name="Alfresco V2.0" version="1.5">
<types>
<!-- ID and version to support SOLR cloud -->
<fieldType name="identifier" class="solr.StrField" sortMissingLast="true" positionIncrementGap="100" />
<fieldType name="version" class="solr.TrieLongField" precisionStep="64" positionIncrementGap="0" />
<fieldType name="lowercase_id" class="solr.TextField" sortMissingLast="true" omitNorms="true" positionIncrementGap="100" >
<analyzer>
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory" />
</analyzer>
</fieldType>
<!-- Fixed analysis for numeric types - query, range, facet, and sorting-->
<fieldType name="int" class="solr.TrieIntField" precisionStep="4" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="float" class="solr.TrieFloatField" precisionStep="4" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="long" class="solr.TrieLongField" precisionStep="6" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="double" class="solr.TrieDoubleField" precisionStep="6" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="date" class="solr.TrieDateField" precisionStep="6" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<!-- Fixed stats analysis for numeric types - stats -->
<fieldType name="stats_int" class="solr.TrieIntField" precisionStep="32" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="stats_float" class="solr.TrieFloatField" precisionStep="64" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="stats_long" class="solr.TrieLongField" precisionStep="32" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="stats_double" class="solr.TrieDoubleField" precisionStep="64" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<fieldType name="stats_date" class="solr.TrieDateField" precisionStep="64" positionIncrementGap="100" sortMissingLast="false" sortMissingFirst="false" />
<!-- All text fields are handled via the Alfresco data model -->
<!-- All anlysis is defined in dummy field types which this field type uses -->
<!-- TODO: truncated text field for sorting?? -->
<fieldType name="oldStandardAnalysis" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.ASCIIFoldingFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="alfrescoFieldType" class="org.alfresco.solr.AlfrescoFieldType" />
<fieldType name="alfrescoCollatableMLTextFieldType" class="org.alfresco.solr.AlfrescoCollatableMLTextFieldType" sortMissingLast="true" />
<fieldType name="path" class="solr.TextField" >
<analyzer class="org.alfresco.repo.search.impl.lucene.analysis.PathAnalyzer" />
</fieldType>
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
spatialContextFactory="org.locationtech.spatial4j.context.SpatialContextFactory"
distErrPct="0.025"
maxDistErr="0.000009"
distanceUnits="degrees" />
<!-- Suggestion -->
<fieldType name="text_shingle" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.ICUTokenizerFactory"/>
<filter class="solr.ICUNormalizer2FilterFactory" name="nfkc_cf" mode="compose" />
<filter class="solr.ShingleFilterFactory" minShingleSize="2" maxShingleSize="3" outputUnigrams="true" outputUnigramsIfNoShingles="true" tokenSeparator=" " />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.ICUTokenizerFactory"/>
<filter class="solr.ICUNormalizer2FilterFactory" name="nfkc_cf" mode="compose" />
<filter class="solr.ShingleFilterFactory" minShingleSize="2" maxShingleSize="3" outputUnigrams="false" outputUnigramsIfNoShingles="true" tokenSeparator=" " />
</analyzer>
</fieldType>
<fieldType name="text_min_hash" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.ICUTokenizerFactory"/>
<filter class="solr.ICUNormalizer2FilterFactory" name="nfkc_cf" mode="compose" />
<filter class="solr.ShingleFilterFactory" minShingleSize="5" maxShingleSize="5" outputUnigrams="false" outputUnigramsIfNoShingles="false" tokenSeparator=" " />
<!--
<filter class="org.apache.lucene.analysis.minhash.ContextAccumulatingFilterFactory" />
-->
<filter class="org.apache.lucene.analysis.minhash.MinHashFilterFactory" hashCount="1" hashSetSize="1" bucketCount="512" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.KeywordTokenizerFactory" />
</analyzer>
</fieldType>
<fieldType name="text_plain" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.ICUTokenizerFactory"/>
</analyzer>
</fieldType>
<!-- Cross-locale -->
<!-- Include word splitting -->
<fieldType name="text___" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.ICUTokenizerFactory"/>
<!-- <tokenizer class="org.apache.solr.analysis.WhitespaceTokenizerFactory" /> -->
<filter class="org.apache.solr.analysis.WordDelimiterFilterFactory"
generateWordParts="1"
generateNumberParts="1"
catenateWords="1"
catenateNumbers="1"
catenateAll="1"
splitOnCaseChange="1"
splitOnNumerics="1"
preserveOriginal="1"
stemEnglishPossessive="1"/>
<filter class="solr.ICUFoldingFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="alfrescoCollatableTextFieldType" class="org.alfresco.solr.AlfrescoCollatableTextFieldType" sortMissingLast="true" />
<!-- -->
<!-- Field types that are only used to define language specific analysis (generally ordered by ISO code) -->
<!-- -->
<!-- English -->
<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- 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.ICUFoldingFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
<filter class="solr.CommonGramsFilterFactory" words="lang/stopwords_en.txt" ignoreCase="true"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.ICUFoldingFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
<filter class="solr.CommonGramsQueryFilterFactory" words="lang/stopwords_en.txt" ignoreCase="true"/>
</analyzer>
</fieldType>
<!-- Arabic -->
<fieldType name="text_ar" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- for any non-arabic -->
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ar.txt" />
<!-- normalizes ~ to ~, etc -->
<filter class="solr.ArabicNormalizationFilterFactory"/>
<filter class="solr.ArabicStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Bulgarian -->
<fieldType name="text_bg" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_bg.txt" />
<filter class="solr.BulgarianStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Catalan -->
<fieldType name="text_ca" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- removes l', etc -->
<filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_ca.txt"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ca.txt" />
<filter class="solr.SnowballPorterFilterFactory" language="Catalan"/>
</analyzer>
</fieldType>
<!-- CJK bigram (see text_ja for a Japanese configuration using morphological analysis) -->
<fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- normalize width before bigram, as e.g. half-width dakuten combine -->
<filter class="solr.CJKWidthFilterFactory"/>
<!-- for any non-CJK -->
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.CJKBigramFilterFactory"/>
</analyzer>
</fieldType>
<!-- Kurdish -->
<fieldType name="text_ckb" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SoraniNormalizationFilterFactory"/>
<!-- for any latin text -->
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ckb.txt"/>
<filter class="solr.SoraniStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Czech -->
<fieldType name="text_cz" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_cz.txt" />
<filter class="solr.CzechStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Danish -->
<fieldType name="text_da" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_da.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Danish"/>
</analyzer>
</fieldType>
<!-- German -->
<fieldType name="text_de" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_de.txt" format="snowball" />
<filter class="solr.GermanNormalizationFilterFactory"/>
<filter class="solr.GermanLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.GermanMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="German2"/> -->
</analyzer>
</fieldType>
<!-- Greek -->
<fieldType name="text_el" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- greek specific lowercase for sigma -->
<filter class="solr.GreekLowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="false" words="lang/stopwords_el.txt" />
<filter class="solr.GreekStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Spanish -->
<fieldType name="text_es" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_es.txt" format="snowball" />
<filter class="solr.SpanishLightStemFilterFactory"/>
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Spanish"/> -->
</analyzer>
</fieldType>
<!-- Basque -->
<fieldType name="text_eu" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_eu.txt" />
<filter class="solr.SnowballPorterFilterFactory" language="Basque"/>
</analyzer>
</fieldType>
<!-- Persian -->
<fieldType name="text_fa" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<!-- for ZWNJ -->
<charFilter class="solr.PersianCharFilterFactory"/>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.ArabicNormalizationFilterFactory"/>
<filter class="solr.PersianNormalizationFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fa.txt" />
</analyzer>
</fieldType>
<!-- Finnish -->
<fieldType name="text_fi" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fi.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Finnish"/>
<!-- less aggressive: <filter class="solr.FinnishLightStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- French -->
<fieldType name="text_fr" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- removes l', etc -->
<filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_fr.txt"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fr.txt" format="snowball" />
<filter class="solr.FrenchLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.FrenchMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="French"/> -->
</analyzer>
</fieldType>
<!-- Irish -->
<fieldType name="text_ga" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- removes d', etc -->
<filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_ga.txt"/>
<!-- removes n-, etc. position increments is intentionally false! -->
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/hyphenations_ga.txt"/>
<filter class="solr.IrishLowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ga.txt"/>
<filter class="solr.SnowballPorterFilterFactory" language="Irish"/>
</analyzer>
</fieldType>
<!-- Galician -->
<fieldType name="text_gl" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_gl.txt" />
<filter class="solr.GalicianStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.GalicianMinimalStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Hindi -->
<fieldType name="text_hi" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<!-- normalizes unicode representation -->
<filter class="solr.IndicNormalizationFilterFactory"/>
<!-- normalizes variation in spelling -->
<filter class="solr.HindiNormalizationFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_hi.txt" />
<filter class="solr.HindiStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Hungarian -->
<fieldType name="text_hu" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_hu.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Hungarian"/>
<!-- less aggressive: <filter class="solr.HungarianLightStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Armenian -->
<fieldType name="text_hy" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_hy.txt" />
<filter class="solr.SnowballPorterFilterFactory" language="Armenian"/>
</analyzer>
</fieldType>
<!-- Indonesian -->
<fieldType name="text_id" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_id.txt" />
<!-- for a less aggressive approach (only inflectional suffixes), set stemDerivational to false -->
<filter class="solr.IndonesianStemFilterFactory" stemDerivational="true"/>
</analyzer>
</fieldType>
<!-- Italian -->
<fieldType name="text_it" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- removes l', etc -->
<filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_it.txt"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_it.txt" format="snowball" />
<filter class="solr.ItalianLightStemFilterFactory"/>
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Italian"/> -->
</analyzer>
</fieldType>
<!-- Japanese using morphological analysis (see text_cjk for a configuration using bigramming)
NOTE: If you want to optimize search for precision, use default operator AND in your query
parser config with <solrQueryParser defaultOperator="AND"/> further down in this file. Use
OR if you would like to optimize for recall (default).
-->
<fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
<analyzer>
<!-- Kuromoji Japanese morphological analyzer/tokenizer (JapaneseTokenizer)
Kuromoji has a search mode (default) that does segmentation useful for search. A heuristic
is used to segment compounds into its parts and the compound itself is kept as synonym.
Valid values for attribute mode are:
normal: regular segmentation
search: segmentation useful for search with synonyms compounds (default)
extended: same as search mode, but unigrams unknown words (experimental)
For some applications it might be good to use search mode for indexing and normal mode for
queries to reduce recall and prevent parts of compounds from being matched and highlighted.
Use <analyzer type="index"> and <analyzer type="query"> for this and mode normal in query.
Kuromoji also has a convenient user dictionary feature that allows overriding the statistical
model with your own entries for segmentation, part-of-speech tags and readings without a need
to specify weights. Notice that user dictionaries have not been subject to extensive testing.
User dictionary attributes are:
userDictionary: user dictionary filename
userDictionaryEncoding: user dictionary encoding (default is UTF-8)
See lang/userdict_ja.txt for a sample user dictionary file.
Punctuation characters are discarded by default. Use discardPunctuation="false" to keep them.
See http://wiki.apache.org/solr/JapaneseLanguageSupport for more on Japanese language support.
-->
<tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
<!--<tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>-->
<!-- Reduces inflected verbs and adjectives to their base/dictionary forms (~~~) -->
<filter class="solr.JapaneseBaseFormFilterFactory"/>
<!-- Removes tokens with certain part-of-speech tags -->
<filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
<!-- Normalizes full-width romaji to half-width and half-width kana to full-width (Unicode NFKC subset) -->
<filter class="solr.CJKWidthFilterFactory"/>
<!-- Removes common tokens typically not useful for search, but have a negative effect on ranking -->
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
<!-- Normalizes common katakana spelling variations by removing any last long sound character (U+30FC) -->
<filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
<!-- Lower-cases romaji characters -->
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- Latvian -->
<fieldType name="text_lv" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_lv.txt" />
<filter class="solr.LatvianStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Dutch -->
<fieldType name="text_nl" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_nl.txt" format="snowball" />
<filter class="solr.StemmerOverrideFilterFactory" dictionary="lang/stemdict_nl.txt" ignoreCase="false"/>
<filter class="solr.SnowballPorterFilterFactory" language="Dutch"/>
</analyzer>
</fieldType>
<!-- Norwegian -->
<fieldType name="text_no" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_no.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Norwegian"/>
<!-- less aggressive: <filter class="solr.NorwegianLightStemFilterFactory" variant="nb"/> -->
<!-- singular/plural: <filter class="solr.NorwegianMinimalStemFilterFactory" variant="nb"/> -->
<!-- The "light" and "minimal" stemmers support variants: nb=Bokml, nn=Nynorsk, no=Both -->
</analyzer>
</fieldType>
<!-- Portuguese -->
<fieldType name="text_pt" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_pt.txt" format="snowball" />
<filter class="solr.PortugueseLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.PortugueseMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Portuguese"/> -->
<!-- most aggressive: <filter class="solr.PortugueseStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Romanian -->
<fieldType name="text_ro" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ro.txt" />
<filter class="solr.SnowballPorterFilterFactory" language="Romanian"/>
</analyzer>
</fieldType>
<!-- Russian -->
<fieldType name="text_ru" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ru.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Russian"/>
<!-- less aggressive: <filter class="solr.RussianLightStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Swedish -->
<fieldType name="text_sv" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_sv.txt" format="snowball" />
<filter class="solr.SnowballPorterFilterFactory" language="Swedish"/>
<!-- less aggressive: <filter class="solr.SwedishLightStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Thai -->
<fieldType name="text_th" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="org.apache.lucene.analysis.th.ThaiTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_th.txt" />
</analyzer>
</fieldType>
<!-- Turkish -->
<fieldType name="text_tr" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- <filter class="solr.ApostropheFilterFactory"/> -->
<filter class="solr.TurkishLowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="false" words="lang/stopwords_tr.txt" />
<filter class="solr.SnowballPorterFilterFactory" language="Turkish"/>
</analyzer>
</fieldType>
</types>
<fields>
<!-- For SOLR cloud - should be the node version -->
<field name="_version_" type="version" indexed="false" stored="true" docValues="true" required="true" />
<!-- For block join - currently not used -->
<field name="_root_" type="identifier" indexed="true" stored="false"/>
<!-- Unique identifier - based on DBID -->
<field name="id" type="identifier" indexed="true" omitNorms="true" stored="true" multiValued="false" required="true" docValues="true"/>
<field name="MINHASH" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" required="false" docValues="false"/>
<!-- Special fields -->
<field name="LID" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="PARENT" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<field name="PATH" type="path" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<field name="ANCESTOR" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<field name="QNAME" type="path" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<field name="PRIMARYASSOCQNAME" type="path" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="PRIMARYASSOCTYPEQNAME" type="path" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="ISNODE" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="ASSOCTYPEQNAME" type="path" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<field name="PRIMARYPARENT" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="TYPE" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<field name="ASPECT" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="PROPERTIES" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="NULLPROPERTIES" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="FIELDS" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="FTSSTATUS" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="DBID" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<field name="TXID" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<field name="INTXID" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="ACLTXID" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<field name="INACLTXID" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="TXCOMMITTIME" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="ACLTXCOMMITTIME" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="EXCEPTIONMESSAGE" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="EXCEPTIONSTACK" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<field name="ACLID" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<field name="READER" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="DENIED" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="OWNER" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="PARENTASSOCCRC" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="TENANT" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<field name="CASCADETX" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<!-- Used to store the last transaction and acl transaction for real time get -->
<field name="S_TXID" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="S_INTXID" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false"/>
<field name="S_ACLTXID" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="S_INACLTXID" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="S_TXCOMMITTIME" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<field name="S_ACLTXCOMMITTIME" type="long" indexed="true" omitNorms="true" stored="true" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<!-- SOLR doc type -->
<field name="DOC_TYPE" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<!-- GEO -->
<field name="GEO" type="location_rpt" indexed="true" stored="false" multiValued="false" sortMissingLast="true" />
<!-- Suggestion -->
<field name="suggest" type="text_shingle" indexed="true" omitNorms="true" omitPositions="true" stored="false" multiValued="true" />
<field name="min_hash" type="text_min_hash" indexed="true" omitNorms="true" omitPositions="true" stored="false" multiValued="true" />
<!-- Dummy field used to create no matches for unknonw properties -->
<field name="_dummy_" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<!-- example pluggable field -->
<field name="SITE" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="TAG" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="PNAME" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="NPATH" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="ANAME" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<field name="APATH" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<!-- Single value indexed -->
<dynamicField name="any@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="encrypted@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="int@s_@*" type="int" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="long@s_@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="float@s_@*" type="float" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="double@s_@*" type="double" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="date@s_@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="datetime@s_@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="boolean@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="qname@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="category@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="noderef@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="noderef@sd_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" sortMissingLast="true" />
<dynamicField name="childassocref@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="assocref@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="path@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="locale@s_@*" type="lowercase_id" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="period@s_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<!-- Single + doc values value wildcard matches -->
<dynamicField name="int@sd@*" type="int" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="long@sd@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="float@sd@*" type="float" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="double@sd@*" type="double" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="date@sd@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="datetime@sd@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<!-- Multi - value indexed -->
<dynamicField name="any@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="encrypted@m_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="int@m_@*" type="int" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="long@m_@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="float@m_@*" type="float" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="double@m_@*" type="double" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="date@m_@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="datetime@m_@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="boolean@m_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="boolean@md_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" sortMissingLast="true"/>
<dynamicField name="qname@m_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="qname@md_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" sortMissingLast="true"/>
<dynamicField name="category@m_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="category@md_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" sortMissingLast="true" />
<dynamicField name="category@sd_@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<dynamicField name="noderef@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="noderef@md@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" sortMissingLast="true" />
<dynamicField name="childassocref@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="assocref@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="path@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="locale@m_@*" type="lowercase_id" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="period@m_@*" type="oldStandardAnalysis" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<!-- Multi + doc values value wildcard matches -->
<dynamicField name="int@md@*" type="int" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="long@md@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="float@md@*" type="float" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="double@md@*" type="double" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="date@md@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="datetime@md@*" type="date" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="text@s____@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="text@s__l_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="false" />
<dynamicField name="text@s__lt@*" type="alfrescoFieldType" indexed="true" omitNorms="false" stored="false" multiValued="false" />
<dynamicField name="text@s___t@*" type="text___" indexed="true" omitNorms="false" stored="false" multiValued="false" />
<dynamicField name="text@s__sort@*" type="alfrescoCollatableTextFieldType" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="text@sd___@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<dynamicField name="text@m____@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="text@m__l_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="text@m__lt@*" type="alfrescoFieldType" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<dynamicField name="text@m___t@*" type="text___" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<dynamicField name="text@md___@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="mltext@m____@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="mltext@m__l_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="mltext@m__lt@*" type="alfrescoFieldType" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<dynamicField name="mltext@m___t@*" type="text___" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<dynamicField name="mltext@m__sort@*" type="alfrescoCollatableMLTextFieldType" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="content@s__size@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="content@s__locale@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<dynamicField name="content@s__mimetype@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<dynamicField name="content@s__encoding@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="true" />
<dynamicField name="content@s__docid@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" docValues="true" sortMissingLast="false" sortMissingFirst="false" />
<dynamicField name="content@s__tr_ex@*" type="text___" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="content@s__tr_time@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="content@s__tr_status@*" type="lowercase_id" indexed="true" omitNorms="true" stored="false" multiValued="false" sortMissingLast="true" />
<dynamicField name="content@s____@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="false" termPositions="false" />
<dynamicField name="content@s__l_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="false" termPositions="false" />
<dynamicField name="content@s__lt@*" type="alfrescoFieldType" indexed="true" omitNorms="false" stored="false" multiValued="false" />
<dynamicField name="content@s___t@*" type="text___" indexed="true" omitNorms="false" stored="false" multiValued="false" />
<dynamicField name="content@m__size@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="content@m__locale@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="content@m__mimetype@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="content@m__encoding@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="content@m__docid@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="true" docValues="true" />
<dynamicField name="content@m__tr_ex@*" type="text___" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="content@m__tr_time@*" type="long" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="content@m__tr_status@*" type="lowercase_id" indexed="true" omitNorms="true" stored="false" multiValued="true" />
<dynamicField name="content@m____@*" type="identifier" indexed="true" omitNorms="true" stored="false" multiValued="true" termPositions="false" />
<dynamicField name="content@m__l_@*" type="alfrescoFieldType" indexed="true" omitNorms="true" stored="false" multiValued="true" termPositions="false" />
<dynamicField name="content@m__lt@*" type="alfrescoFieldType" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<dynamicField name="content@m___t@*" type="text___" indexed="true" omitNorms="false" stored="false" multiValued="true" />
<!-- Suggestion -->
<dynamicField name="suggest_*" type="text_shingle" indexed="false" omitNorms="true" stored="false" multiValued="true" />
<copyField source="suggest_*" dest="suggest" />
</fields>
<uniqueKey>id</uniqueKey>
</schema>