Feature/search 2171 datamodel change

This commit is contained in:
Andrea Gazzarini
2020-05-04 14:02:28 +01:00
parent cb3897c80d
commit 7644350310
9 changed files with 385 additions and 28 deletions
@@ -152,6 +152,23 @@ public class AlfrescoSolrDataModel implements QueryConstants
}
public static final String CONTENT_S_LOCALE_PREFIX = "content@s__locale@";
static final String PART_FIELDNAME_PREFIX = "part@sd@";
/**
* Infix used for denoting a primitive single valued field with no doc values enabled
*
* @see #getFieldForText
*/
private static final String SINGLE_VALUE_WITHOUT_DOC_VALUES_MARKER = "@s_@";
/**
* Infix used for denoting a primitive single valued field with no doc values enabled
*
* @see #getFieldForText
*/
private static final String SINGLE_VALUE_WITH_DOC_VALUES_MARKER = "@sd@";
static final String SHARED_PROPERTIES = "shared.properties";
protected final static Logger log = LoggerFactory.getLogger(AlfrescoSolrDataModel.class);
@@ -760,7 +777,6 @@ public class AlfrescoSolrDataModel implements QueryConstants
private void addHighlightSearchFields( PropertyDefinition propertyDefinition , IndexedField indexedField)
{
QName propertyName = propertyDefinition.getName();
QName propertyDataTypeQName = propertyDefinition.getDataType().getName();
String fieldName;
@@ -1416,7 +1432,7 @@ public class AlfrescoSolrDataModel implements QueryConstants
public static class IndexedField
{
private List<FieldInstance> fields = new LinkedList<>();
private final List<FieldInstance> fields = new LinkedList<>();
public List<FieldInstance> getFields()
{
@@ -1747,4 +1763,27 @@ public class AlfrescoSolrDataModel implements QueryConstants
getNamespaceDAO().addPrefix("", NamespaceService.CONTENT_MODEL_1_0_URI);
}
}
/**
* Returns the prefix used for denoting a field which is meant to represent a constituent part of a
* date or datetime (e.g. year, month, second, minute).
*
* @param sourceFieldName the date/datetime source field name.
* @param sourceDataTypeDefinition the datatype of the source field name (date or datetime)
* @return the prefix that can be used for denoting a date or datetime part
*/
public String destructuredDateTimePartFieldNamePrefix(String sourceFieldName, DataTypeDefinition sourceDataTypeDefinition) {
// source field name example: datetime@sd@{http://www.alfresco.org/model/content/1.0}created
// prefix: datetime
String prefix = sourceFieldName.substring(0, sourceFieldName.indexOf("@"));
// prefix, docValues disabled option: datetime@s_@
String sourceFieldNamePrefixWithoutDocValues = prefix + SINGLE_VALUE_WITHOUT_DOC_VALUES_MARKER;
// prefix, docValues enabled option: datetime@sd@
String sourceFieldNamePrefixWithDocValues = prefix + SINGLE_VALUE_WITH_DOC_VALUES_MARKER;
return sourceFieldName.replace(sourceFieldNamePrefixWithoutDocValues, PART_FIELDNAME_PREFIX)
.replace(sourceFieldNamePrefixWithDocValues, PART_FIELDNAME_PREFIX);
}
}
@@ -72,6 +72,7 @@ import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_VERSI
import static org.alfresco.solr.AlfrescoSolrDataModel.getAclChangeSetDocumentId;
import static org.alfresco.solr.AlfrescoSolrDataModel.getAclDocumentId;
import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
import static org.alfresco.util.ISO8601DateFormat.isTimeComponentDefined;
import java.io.File;
import java.io.IOException;
@@ -80,6 +81,8 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -118,6 +121,7 @@ import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.NamespaceDAO;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -363,6 +367,14 @@ public class SolrInformationServer implements InformationServer
/** Shared property to determine if the cascade tracking is enabled. */
public static final String CASCADE_TRACKER_ENABLED = "alfresco.cascade.tracker.enabled";
private static final String UNIT_OF_TIME_FIELD_INFIX = "_unit_of_time";
static final String UNIT_OF_TIME_YEAR_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_year";
static final String UNIT_OF_TIME_MONTH_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_month";
static final String UNIT_OF_TIME_DAY_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_day";
static final String UNIT_OF_TIME_HOUR_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_hour";
static final String UNIT_OF_TIME_MINUTE_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_minute";
static final String UNIT_OF_TIME_SECOND_FIELD_SUFFIX = UNIT_OF_TIME_FIELD_INFIX + "_second";
private final static Function<String, List<Object>> LAZY_EMPTY_MUTABLE_LIST = key -> new ArrayList<>();
private final AlfrescoCoreAdminHandler adminHandler;
@@ -403,6 +415,8 @@ public class SolrInformationServer implements InformationServer
private String skippingDocsQueryString;
private boolean isSkippingDocsInitialized;
private final boolean dateFieldDestructuringHasBeenEnabledOnThisInstance;
static class DocListCollector implements Collector, LeafCollector
{
private final IntArrayList docs = new IntArrayList();
@@ -580,6 +594,11 @@ public class SolrInformationServer implements InformationServer
port = portNumber(props);
baseUrl = baseUrl(props);
dateFieldDestructuringHasBeenEnabledOnThisInstance = Boolean.parseBoolean(coreConfiguration.getProperty("alfresco.destructureDateFields", "true"));
LOGGER.info(
"Date fields destructuring has been {} on this instance.",
dateFieldDestructuringHasBeenEnabledOnThisInstance ? "enabled" : "disabled");
}
@Override
@@ -2191,7 +2210,7 @@ public class SolrInformationServer implements InformationServer
}
}
static void mltextProperty(QName propertyQName, MLTextPropertyValue value, final BiConsumer<String, Object> valueHolder)
void mltextProperty(QName propertyQName, MLTextPropertyValue value, final BiConsumer<String, Object> valueHolder)
{
AlfrescoSolrDataModel dataModel = AlfrescoSolrDataModel.getInstance();
List<FieldInstance> fields = dataModel.getIndexedFieldNamesForProperty(propertyQName).getFields();
@@ -2204,7 +2223,7 @@ public class SolrInformationServer implements InformationServer
.forEach(field -> addMLTextProperty(valueHolder, field, value));
}
static void stringProperty(QName propertyQName, StringPropertyValue value, PropertyValue locale, final BiConsumer<String, Object> valueHolder)
void stringProperty(QName propertyQName, StringPropertyValue value, PropertyValue locale, final BiConsumer<String, Object> valueHolder)
{
AlfrescoSolrDataModel dataModel = AlfrescoSolrDataModel.getInstance();
PropertyDefinition definition = dataModel.getPropertyDefinition(propertyQName);
@@ -2217,15 +2236,21 @@ public class SolrInformationServer implements InformationServer
dataModel.getIndexedFieldNamesForProperty(propertyQName).getFields()
.stream()
.filter(field -> field.getField().startsWith("text@sd___@"))
.forEach(field -> addStringProperty(valueHolder, field, value, locale));
.forEach(field -> addStringProperty(valueHolder, field, value, locale, definition));
} else
{
dataModel.getIndexedFieldNamesForProperty(propertyQName).getFields()
.forEach(field -> addStringProperty(valueHolder, field, value, locale));
.forEach(field -> {
if (canBeDestructured(definition, field.getField()))
{
setUnitOfTimeFields(valueHolder, field.getField(), value.getValue(), definition.getDataType());
}
addStringProperty(valueHolder, field, value, locale, definition);
});
}
}
static void populateProperties(
void populateProperties(
Map<QName, PropertyValue> properties,
boolean contentIndexingHasBeenRequestedForThisNode,
SolrInputDocument document,
@@ -2384,7 +2409,7 @@ public class SolrInformationServer implements InformationServer
}
}
private static void addContentPropertyMetadata(
private void addContentPropertyMetadata(
BiConsumer<String, Object> consumer,
QName propertyQName,
ContentPropertyValue contentPropertyValue,
@@ -2424,7 +2449,7 @@ public class SolrInformationServer implements InformationServer
*
* @see #insertContentUpdateMarker(SolrInputDocument, ContentPropertyValue)
*/
private static void markAsContentInSynch(SolrInputDocument document)
private void markAsContentInSynch(SolrInputDocument document)
{
markAsContentInSynch(document, (ContentPropertyValue)null);
}
@@ -2435,7 +2460,7 @@ public class SolrInformationServer implements InformationServer
*
* @see #insertContentUpdateMarker(SolrInputDocument, ContentPropertyValue)
*/
private static void markAsContentInSynch(SolrInputDocument document, ContentPropertyValue value)
private void markAsContentInSynch(SolrInputDocument document, ContentPropertyValue value)
{
ofNullable(value)
.map(ContentPropertyValue::getId)
@@ -2450,7 +2475,7 @@ public class SolrInformationServer implements InformationServer
*
* @see #insertContentUpdateMarker(SolrInputDocument, ContentPropertyValue)
*/
private static void markAsContentInSynch(SolrInputDocument document, Long id)
private void markAsContentInSynch(SolrInputDocument document, Long id)
{
long contentVersionId = ofNullable(id).orElse(CONTENT_UPDATED_MARKER);
@@ -2513,7 +2538,7 @@ public class SolrInformationServer implements InformationServer
*
* As a side note, please keep in mind the FTSSTATUS field has been removed.
*/
private static void insertContentUpdateMarker(SolrInputDocument document, ContentPropertyValue value)
private void insertContentUpdateMarker(SolrInputDocument document, ContentPropertyValue value)
{
ofNullable(value)
.map(ContentPropertyValue::getId)
@@ -2524,7 +2549,7 @@ public class SolrInformationServer implements InformationServer
() -> document.setField(LAST_INCOMING_CONTENT_VERSION_ID, CONTENT_OUTDATED_MARKER));
}
private static void addContentProperty(
private void addContentProperty(
BiConsumer<String, Object> consumer,
SolrInputDocument document,
QName propertyName,
@@ -2629,7 +2654,7 @@ public class SolrInformationServer implements InformationServer
return indexOfSeparator == -1 ? locale : locale.substring(0, indexOfSeparator);
}
private static List<String> getLocalisedValues(MLTextPropertyValue mlTextPropertyValue)
private List<String> getLocalisedValues(MLTextPropertyValue mlTextPropertyValue)
{
if (mlTextPropertyValue == null)
{
@@ -2650,7 +2675,7 @@ public class SolrInformationServer implements InformationServer
return values;
}
private static void addMLTextProperty(BiConsumer<String, Object> consumer, FieldInstance field, MLTextPropertyValue mlTextPropertyValue)
private void addMLTextProperty(BiConsumer<String, Object> consumer, FieldInstance field, MLTextPropertyValue mlTextPropertyValue)
{
if (mlTextPropertyValue == null)
{
@@ -3700,7 +3725,7 @@ public class SolrInformationServer implements InformationServer
}
}
private static String getLocalisedValue(StringPropertyValue property, PropertyValue localeProperty)
private String getLocalisedValue(StringPropertyValue property, PropertyValue localeProperty)
{
Locale locale =
ofNullable(localeProperty)
@@ -3712,12 +3737,80 @@ public class SolrInformationServer implements InformationServer
return "\u0000" + locale.getLanguage() + "\u0000" + property.getValue();
}
private static void addStringProperty(BiConsumer<String, Object> consumer, FieldInstance field, StringPropertyValue property, PropertyValue localeProperty)
private void addStringProperty(BiConsumer<String, Object> consumer, FieldInstance field, StringPropertyValue property, PropertyValue localeProperty, PropertyDefinition definition)
{
consumer.accept(field.getField(), field.isLocalised() ? getLocalisedValue(property, localeProperty) : property.getValue());
}
private static void addFieldIfNotSet(SolrInputDocument doc, String name)
/**
* Checks if the given property definition refers to a field that can be destructured in parts.
* A field can be destructured only if:
*
* <ul>
* <li>It is not multivalued</li>
* <li>Destructuring is not disabled in configuration</li>
* <li>Its data type is {@link DataTypeDefinition#DATE} or {@link DataTypeDefinition#DATETIME}</li>
* </ul>
*/
boolean canBeDestructured(PropertyDefinition definition, String fieldName)
{
return !definition.isMultiValued() &&
fieldName != null &&
fieldName.contains("@") && // avoid static date/datetime fields
dateFieldDestructuringHasBeenEnabledOnThisInstance &&
(definition.getDataType().getName().equals(DataTypeDefinition.DATETIME) ||
definition.getDataType().getName().equals(DataTypeDefinition.DATE));
}
/**
* To add support for SQL date functions SEARCH-2171 introduced date and datetime fields destructuration.
* Other than being indexed as plain TrieDate fields, date and datetime fields are also indexed in separate fields that
* contain their constituent parts.
*
* Date fields:
*
* <ul>
* <li>YEAR</li>
* <li>MONTH (1-12)</li>
* <li>DAY (OF THE MONTH)</li>
* </ul>
*
* Datetime fields also add the following:
*
* <ul>
* <li>HOUR (0-23)</li>
* <li>MINUTE</li>
* <li>SECOND</li>
* </ul>
*
* Note the destructured parts are not localised to a specific Timezone: they are always expressed in UTC.
*
* @see <a href="https://issues.alfresco.com/jira/browse/SEARCH-2171">SEARCH-2171</a>
*/
void setUnitOfTimeFields(BiConsumer<String, Object> consumer, String sourceFieldName, String value, DataTypeDefinition dataType)
{
try
{
String fieldNamePrefix = dataModel.destructuredDateTimePartFieldNamePrefix(sourceFieldName, dataType);
ZonedDateTime dateTime = ZonedDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME);
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_YEAR_FIELD_SUFFIX, dateTime.getYear());
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_MONTH_FIELD_SUFFIX, dateTime.getMonth().getValue());
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_DAY_FIELD_SUFFIX, dateTime.getDayOfMonth());
if (DataTypeDefinition.DATETIME.equals(dataType.getName()) && isTimeComponentDefined(value))
{
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_MINUTE_FIELD_SUFFIX, dateTime.getMinute());
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_HOUR_FIELD_SUFFIX, dateTime.getHour());
consumer.accept(fieldNamePrefix + UNIT_OF_TIME_SECOND_FIELD_SUFFIX, dateTime.getSecond());
}
}
catch (Exception exception)
{
LOGGER.error("Unable to destructure date/datetime value {} (Field was {})", value, sourceFieldName, exception);
}
}
private void addFieldIfNotSet(SolrInputDocument doc, String name)
{
doc.addField(FIELD_FIELDS, name);
}
@@ -3796,12 +3889,12 @@ public class SolrInformationServer implements InformationServer
return searchers;
}
private static void clearFields(SolrInputDocument document, List<String> fields)
private void clearFields(SolrInputDocument document, List<String> fields)
{
notNullOrEmpty(fields).forEach(document::removeField);
}
private static void clearFields(SolrInputDocument document, String... fields)
private void clearFields(SolrInputDocument document, String... fields)
{
stream(notNullOrEmpty(fields)).forEach(document::removeField);
}
@@ -660,7 +660,19 @@
<!-- 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" />
<!--
SEARCH-2171 introduced these fields for capturing the destructured parts of a date/datetime field.
An investigation [1] revealed the optimal field/field type definition for these fields doesn't match any current
field / field type currently defined. The closest is the "int" datatype but we still need to override some option.
As consequence of that, until we work on a refactoring ticket for primitive datatypes, we need to define a specific
field for destructured date / datetime parts.
[1] https://issues.alfresco.com/jira/browse/SEARCH-2171?focusedCommentId=767859&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-767859
-->
<dynamicField name="part@sd@*" type="int" indexed="true" stored="false" docValues="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" />
@@ -168,6 +168,16 @@ alfresco.metadata.ignore.datatype.1=app:configurations
alfresco.metadata.skipDescendantDocsForSpecificAspects=false
#alfresco.metadata.ignore.aspect.0=
# Date/Datetime fields only: if this property is set to true (default value) each date/datetime field
#
# - will be indexed as a whole value
# - will generate additional fields corresponding to its constituent parts (year, month, day, hour, minute, second)
#
# If this property is set to false then the only the whole value is indexed.
# This will result in a smaller index, but date function support will be disabled.
#
# alfresco.destructureDateFields=true
# The number of matches from the index to include when rewriting wildcard search terms as an OR-ed list.
alfresco.topTermSpanRewriteLimit=1000
@@ -1067,6 +1067,18 @@
<!-- Single value indexed -->
<dynamicField name="any@s_@*" type="oldStandardAnalysis" sortMissingLast="true" />
<dynamicField name="encrypted@s_@*" type="oldStandardAnalysis" sortMissingLast="true" />
<!--
SEARCH-2171 introduced these fields for capturing the destructured parts of a date/datetime field.
An investigation [1] revealed the optimal field/field type definition for these fields doesn't match any current
field / field type currently defined. The closest is the "int" datatype but we still need to override some option.
As consequence of that, until we work on a refactoring ticket for primitive datatypes, we need to define a specific
field for destructured date / datetime parts.
[1] https://issues.alfresco.com/jira/browse/SEARCH-2171?focusedCommentId=767859&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-767859
-->
<dynamicField name="part@sd@*" type="int" indexed="true" stored="false" docValues="true"/>
<dynamicField name="int@s_@*" type="int" />
<dynamicField name="long@s_@*" type="long" />
<dynamicField name="float@s_@*" type="float" />
@@ -165,6 +165,16 @@ alfresco.metadata.ignore.datatype.1=app:configurations
alfresco.metadata.skipDescendantDocsForSpecificAspects=false
#alfresco.metadata.ignore.aspect.0=
# Date/Datetime fields only: if this property is set to true (default value) each date/datetime field
#
# - will be indexed as a whole value
# - will generate additional fields corresponding to its constituent parts (year, month, day, hour, minute, second)
#
# If this property is set to false then the only the whole value is indexed.
# This will result in a smaller index, but date function support will be disabled.
#
# alfresco.destructureDateFields=true
# The number of matches from the index to include when rewriting wildcard search terms as an OR-ed list.
alfresco.topTermSpanRewriteLimit=1000
@@ -86,7 +86,7 @@ public class AlfrescoSolrReloadIT extends AbstractAlfrescoSolrIT
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world")), numNodes, 100000);
Collection<Tracker> trackers = getTrackers();
int numOfTrackers = trackers.size();
int jobs = getJobsCount();
@@ -125,4 +125,4 @@ public class AlfrescoSolrReloadIT extends AbstractAlfrescoSolrIT
{
return admin.getScheduler().getJobsCount();
}
}
}
@@ -477,11 +477,14 @@ public class AlfrescoSolrUtils
SolrServletRequest solrQueryRequest = null;
try
{
AlfrescoCoreAdminHandler admin = (AlfrescoCoreAdminHandler) core.getCoreContainer().getMultiCoreHandler();
SolrInformationServer solrInformationServer = (SolrInformationServer) admin.getInformationServers().get(core.getName());
solrQueryRequest = new SolrServletRequest(core, null);
AddUpdateCommand addDocCmd = new AddUpdateCommand(solrQueryRequest);
addDocCmd.overwrite = true;
addDocCmd.solrDoc = createDocument(dataModel, new Long(txid), new Long(dbid), nodeRef, type, aspects,
properties, content, new Long(aclid), paths, owner, parentAssocs, ancestors);
properties, content, new Long(aclid), paths, owner, parentAssocs, ancestors, solrInformationServer);
core.getUpdateHandler().addDoc(addDocCmd);
if (commit)
{
@@ -528,7 +531,8 @@ public class AlfrescoSolrUtils
String[] paths,
String owner,
ChildAssociationRef[] parentAssocs,
NodeRef[] ancestors)throws IOException
NodeRef[] ancestors,
SolrInformationServer solrInformationServer)
{
SolrInputDocument doc = new SolrInputDocument();
String id = AlfrescoSolrDataModel.getNodeDocumentId(AlfrescoSolrDataModel.DEFAULT_TENANT, dbid);
@@ -615,7 +619,7 @@ public class AlfrescoSolrUtils
{
final boolean isContentIndexedForNode = true;
final boolean transformContentFlag = true;
SolrInformationServer.populateProperties(properties, isContentIndexedForNode, doc, transformContentFlag);
solrInformationServer.populateProperties(properties, isContentIndexedForNode, doc, transformContentFlag);
if (content != null)
{
addContentToDoc(doc, content);
@@ -740,12 +744,15 @@ public class AlfrescoSolrUtils
SolrServletRequest solrQueryRequest = null;
try
{
AlfrescoCoreAdminHandler admin = (AlfrescoCoreAdminHandler) core.getCoreContainer().getMultiCoreHandler();
SolrInformationServer solrInformationServer = (SolrInformationServer) admin.getInformationServers().get(core.getName());
solrQueryRequest = new SolrServletRequest(core, null);
AddUpdateCommand addDocCmd = new AddUpdateCommand(solrQueryRequest);
addDocCmd.overwrite = true;
addDocCmd.solrDoc = createDocument(dataModel, new Long(txid), new Long(dbid), rootNodeRef,
ContentModel.TYPE_STOREROOT, new QName[]{ContentModel.ASPECT_ROOT}, null, null, new Long(aclid),
new String[]{"/"}, "system", null, null);
new String[]{"/"}, "system", null, null, solrInformationServer);
core.getUpdateHandler().addDoc(addDocCmd);
addAcl(solrQueryRequest, core, dataModel, acltxid, aclid, 0, 0);
AddUpdateCommand txCmd = new AddUpdateCommand(solrQueryRequest);
@@ -20,9 +20,13 @@
package org.alfresco.solr;
import java.util.Properties;
import java.util.stream.Stream;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.solr.client.SOLRAPIClient;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.SolrCore;
@@ -34,11 +38,40 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static java.util.Optional.ofNullable;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.ANY;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.ASSOC_REF;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.BOOLEAN;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.CATEGORY;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.CHILD_ASSOC_REF;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.CONTENT;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.DOUBLE;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.ENCRYPTED;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.FLOAT;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.INT;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.LOCALE;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.LONG;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.MLTEXT;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.NODE_REF;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.PATH;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.PERIOD;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.QNAME;
import static org.alfresco.service.cmr.dictionary.DataTypeDefinition.TEXT;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_DAY_FIELD_SUFFIX;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_HOUR_FIELD_SUFFIX;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_MINUTE_FIELD_SUFFIX;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_MONTH_FIELD_SUFFIX;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_SECOND_FIELD_SUFFIX;
import static org.alfresco.solr.SolrInformationServer.UNIT_OF_TIME_YEAR_FIELD_SUFFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -112,6 +145,147 @@ public class SolrInformationServerTest
assertSame(state, document);
}
@Test
public void setUnitOfTimeFieldsWithDatetimeField_shouldSetDateAndTimeFields()
{
DataTypeDefinition datatype = mock(DataTypeDefinition.class);
when(datatype.getName()).thenReturn(DataTypeDefinition.DATETIME);
SolrInputDocument document = new SolrInputDocument();
String fieldPrefix = "datetime@sd@";
String fieldSuffix = "{http://www.alfresco.org/model/content/1.0}created";
String sourceFieldName = fieldPrefix + fieldSuffix;
infoServer.setUnitOfTimeFields(document::setField, sourceFieldName, "1972-09-16T17:33:18Z", datatype);
assertEquals(1972,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_YEAR_FIELD_SUFFIX));
assertEquals(9,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_MONTH_FIELD_SUFFIX));
assertEquals(16,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_DAY_FIELD_SUFFIX));
assertEquals(17,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_HOUR_FIELD_SUFFIX));
assertEquals(33,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_MINUTE_FIELD_SUFFIX));
assertEquals(18,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_SECOND_FIELD_SUFFIX));
}
@Test
public void setUnitOfTimeFieldsWithDatetimeField_shouldSetOnlyDateFields()
{
DataTypeDefinition datatype = mock(DataTypeDefinition.class);
when(datatype.getName()).thenReturn(DataTypeDefinition.DATE);
SolrInputDocument document = new SolrInputDocument();
String fieldPrefix = "datetime@sd@";
String fieldSuffix = "{http://www.alfresco.org/model/content/1.0}created";
String sourceFieldName = fieldPrefix + fieldSuffix;
infoServer.setUnitOfTimeFields(document::setField, sourceFieldName, "1972-09-16T17:33:18Z", datatype);
assertEquals(1972,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_YEAR_FIELD_SUFFIX));
assertEquals(9,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_MONTH_FIELD_SUFFIX));
assertEquals(16,
unitOfTimeFieldValue(document, AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_DAY_FIELD_SUFFIX));
assertFalse(document.containsKey(AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_HOUR_FIELD_SUFFIX));
assertFalse(document.containsKey(AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_MINUTE_FIELD_SUFFIX));
assertFalse(document.containsKey(AlfrescoSolrDataModel.PART_FIELDNAME_PREFIX + fieldSuffix + UNIT_OF_TIME_SECOND_FIELD_SUFFIX));
}
@Test
public void destructuringCannotBeAppliedToMultivaluedFields()
{
PropertyDefinition multiValuedProperty = mock(PropertyDefinition.class);
when(multiValuedProperty.isMultiValued()).thenReturn(true);
assertFalse(infoServer.canBeDestructured(multiValuedProperty, "datetime@sd@{http://www.alfresco.org/model/content/1.0}created"));
}
/**
* Destructuring cannot only be applied to those fields matching a dynamic field in the schema.
* The dynamic fields defined in the schema follow the naming:
*
* [datatype]@[options]@[namespace][local name]
*
* For example
*
* datetime@sd@{http://www.alfresco.org/model/content/1.0}created
*
* SearchServices calls those fields "PROPERTIES", while the static fields (even belonging to a Node) are called
* "FIELDS".
*/
@Test
public void destructuringCannotBeAppliedToProperties()
{
PropertyDefinition multiValuedProperty = mock(PropertyDefinition.class);
when(multiValuedProperty.isMultiValued()).thenReturn(false);
assertFalse(
"Destructuring can only be applied to Node properties (e.g. dynamic fields in Solr schema)",
infoServer.canBeDestructured(multiValuedProperty, "this_is_a_field_that_doesnt_follow_the_dynamic_naming"));
}
@Test
public void destructuringCanBeAppliedToDateFields()
{
PropertyDefinition propertyThatCanBeDestructured = mock(PropertyDefinition.class);
when(propertyThatCanBeDestructured.isMultiValued()).thenReturn(false);
DataTypeDefinition date = mock(DataTypeDefinition.class);
when(date.getName()).thenReturn(DataTypeDefinition.DATE);
when(propertyThatCanBeDestructured.getDataType()).thenReturn(date);
assertTrue(
"Destructuring must be supported in Date fields!",
infoServer.canBeDestructured(propertyThatCanBeDestructured,"date@sd@{http://www.alfresco.org/model/content/1.0}created"));
}
@Test
public void destructuringCanBeAppliedToDateTimeFields()
{
PropertyDefinition propertyThatCanBeDestructured = mock(PropertyDefinition.class);
when(propertyThatCanBeDestructured.isMultiValued()).thenReturn(false);
DataTypeDefinition datetime = mock(DataTypeDefinition.class);
when(datetime.getName()).thenReturn(DataTypeDefinition.DATETIME);
when(propertyThatCanBeDestructured.getDataType()).thenReturn(datetime);
assertTrue(
"Destructuring must be supported in Datetime fields!",
infoServer.canBeDestructured(propertyThatCanBeDestructured, "datetime@sd@{http://www.alfresco.org/model/content/1.0}created"));
}
@Test
public void destructuringCanBeAppliedOnlyToDateOrDatetimeFields()
{
Stream.of(ANY,ENCRYPTED,TEXT,MLTEXT,CONTENT,INT,LONG,FLOAT,DOUBLE,
BOOLEAN,QNAME,CATEGORY,NODE_REF,CHILD_ASSOC_REF,ASSOC_REF,
PATH,LOCALE,PERIOD)
.map(qname -> {
PropertyDefinition propertyThatCannotBeDestructured = mock(PropertyDefinition.class);
DataTypeDefinition def = Mockito.mock(DataTypeDefinition.class);
when(def.getName()).thenReturn(qname);
when(propertyThatCannotBeDestructured.getDataType()).thenReturn(def);
return propertyThatCannotBeDestructured;})
.forEach(property -> assertFalse(
"Destructuring must be supported only on Date or Datetime fields!",
infoServer.canBeDestructured(property, "somedatatype@sd@{http://www.alfresco.org/model/content/1.0}somefield")));
}
private int unitOfTimeFieldValue(SolrInputDocument doc, String fieldName) {
return ofNullable(doc.getFieldValue(fieldName))
.map(Number.class::cast)
.map(Number::intValue)
.orElseThrow(() -> new IllegalArgumentException(fieldName + " hasn't been set."));
}
/**
* GetState returns null in case the given id doesn't correspond to an existing state document.
*/
@@ -134,4 +308,4 @@ public class SolrInformationServerTest
assertNull(document);
}
}
}