Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,11 @@ public class ElasticSearchService implements GrailsApplicationAware {
LOG.debug("Deleting all instances of ${scm.domainClass}")
}

// The index is splitted to avoid out of memory exception
def count = scm.domainClass.clazz.count() ?: 0
int nbRun = Math.ceil(count / maxRes)

scm.domainClass.clazz.withNewSession {session ->
// The index is splitted to avoid out of memory exception
def count = scm.domainClass.clazz.count() ?: 0
int nbRun = Math.ceil(count / maxRes)

for(int i = 0; i < nbRun; i++) {
scm.domainClass.clazz.withCriteria {
firstResult(i * maxRes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class DeepDomainClassMarshaller extends DefaultMarshaller {
if (!scm) {
throw new IllegalStateException("Domain class ${domainClass} is not searchable.")
}
for (GrailsDomainClassProperty prop in domainClass.persistantProperties) {
for (GrailsDomainClassProperty prop in domainClass.properties) {
def propertyMapping = scm.getPropertyMapping(prop.name)
if (!propertyMapping) {
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ public class SearchableClassMapping {
private boolean root = true;
private boolean all = true;

/** Identifier properties used for indexing */
private List<String> identityProperties;
private String identitySeparator = ':';

public SearchableClassMapping(GrailsDomainClass domainClass, Collection<SearchableClassPropertyMapping> propertiesMapping) {
this.domainClass = domainClass;
this.propertiesMapping = propertiesMapping;
this.identityProperties = [domainClass.identifier.name];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, indentityProperties will behave as it did before, using the identifier as the sole property for indexId.

}

public SearchableClassPropertyMapping getPropertyMapping(String propertyName) {
Expand Down Expand Up @@ -59,6 +64,22 @@ public class SearchableClassMapping {
return domainClass;
}

public List<String> getIdentityProperties() {
return identityProperties;
}

public void setIdentityProperties(List<String> propertyNames) {
this.identityProperties = propertyNames;
}

public String getIdentitySeparator() {
return identitySeparator;
}

public void setIdentitySeparator(String separator) {
this.identitySeparator = separator;
}

/**
* Validate searchable class mapping.
* @param contextHolder context holding all known searchable mappings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,20 @@ public Collection buildResults(SearchHits hits) {
LOG.warn("Unknown SearchHit: " + hit.id() + "#" + hit.type() + ", domain class name: ");
continue;
}
String domainClassName = scm.getDomainClass().getFullName();

GrailsDomainClassProperty identifier = scm.getDomainClass().getIdentifier();
Object id = typeConverter.convertIfNecessary(hit.id(), identifier.getType());
GroovyObject instance = (GroovyObject) scm.getDomainClass().newInstance();
instance.setProperty(identifier.getName(), id);

// The id stored in the index may be a composition of multiple fields. Decompose it and seat each of the
// values contained there in (excluding non-persistant properties as they're likely transient or read-only)
String[] idValues = hit.id().split(scm.getIdentitySeparator());
for (int i = 0; i < idValues.length; i++) {
String propertyName = scm.getIdentityProperties().get(i);
GrailsDomainClassProperty property = scm.getDomainClass().getPropertyByName(propertyName);
if (property.isPersistent()) {
Object value = typeConverter.convertIfNecessary(idValues[i], property.getType());
instance.setProperty(property.getName(), value);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flip side of adding multiple fields to _id is that we need to parse it back into the Domain instance.


/*def mapContext = elasticSearchContextHolder.getMappingContext(domainClass.propertyName)?.propertiesMapping*/
Map rebuiltProperties = new HashMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.grails.plugins.elasticsearch.index;

import groovy.lang.GroovyObject;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil;
import org.codehaus.groovy.grails.orm.hibernate.support.HibernatePersistenceContextInterceptor;
Expand Down Expand Up @@ -94,13 +96,8 @@ public void setSessionFactory(SessionFactory sessionFactory) {
}

public void addIndexRequest(Object instance) {
addIndexRequest(instance, null);
}

public void addIndexRequest(Object instance, Serializable id) {
synchronized (this) {
IndexEntityKey key = id == null ? new IndexEntityKey(instance) :
new IndexEntityKey(id.toString(), GrailsHibernateUtil.unwrapIfProxy(instance).getClass());
IndexEntityKey key = new IndexEntityKey(instance);
indexRequests.put(key, GrailsHibernateUtil.unwrapIfProxy(instance));
}
}
Expand Down Expand Up @@ -384,7 +381,13 @@ class IndexEntityKey implements Serializable {
if (scm == null) {
throw new IllegalArgumentException("Class " + clazz + " is not a searchable domain class.");
}
this.id = (InvokerHelper.invokeMethod(instance, "ident", null)).toString();

// Set the id based on the fields configured in the indexId searchable property
List<String> idValues = new ArrayList<String>();
for (String property : scm.getIdentityProperties()) {
idValues.add(InvokerHelper.getGroovyObjectProperty((GroovyObject)instance, property).toString());
}
this.id = StringUtils.join(idValues, scm.getIdentitySeparator());
}

public String getId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ class SearchableDomainClassMapper extends GroovyObjectSupport {
/**
* Options applied to searchable class itself
*/
public static final Set<String> CLASS_MAPPING_OPTIONS = new HashSet<String>(Arrays.asList("all", "root", "only", "except"));
public static final Set<String> CLASS_MAPPING_OPTIONS = new HashSet<String>(Arrays.asList("all", "root", "only", "except", "indexId"));
/**
* Searchable property name
*/
public static final String SEARCHABLE_PROPERTY_NAME = "searchable";

/**
* Mapping properties used with 'indexId' to allow for a custom stored key
*/
public static final String INDEX_ID_PROPERTIES_NAME = "properties";
public static final String INDEX_ID_SEPARATOR_NAME = "separator";
public static final Set<String> INDEX_ID_MAPPING_OPTIONS = new HashSet<String>(Arrays.asList(INDEX_ID_PROPERTIES_NAME, INDEX_ID_SEPARATOR_NAME));

/**
* Class mapping properties
*/
Expand All @@ -46,6 +53,7 @@ class SearchableDomainClassMapper extends GroovyObjectSupport {
private GrailsApplication grailsApplication;
private Object only;
private Object except;
private Object indexId;

private ConfigObject esConfig;

Expand Down Expand Up @@ -78,10 +86,19 @@ public void setExcept(Object except) {
this.except = except;
}

public void setIndexId(Object indexId) {
this.indexId = indexId;
}

public void root(Boolean rootFlag) {
this.root = rootFlag;
}


public void indexId(Object indexId) {
this.indexId = indexId;
}

/**
* @return searchable domain class mapping
*/
Expand Down Expand Up @@ -169,9 +186,56 @@ public SearchableClassMapping buildClassMapping() {

SearchableClassMapping scm = new SearchableClassMapping(grailsDomainClass, customMappedProperties.values());
scm.setRoot(root);

// Override the default properties to use as _id in the index
if (indexId != null) {
Map<String, ?> indexIdMap = buildIndexIdMapping();
scm.setIdentityProperties((List<String>)indexIdMap.get(INDEX_ID_PROPERTIES_NAME));
if (indexIdMap.containsKey(INDEX_ID_SEPARATOR_NAME)) {
scm.setIdentitySeparator(indexIdMap.get(INDEX_ID_SEPARATOR_NAME).toString());
}
}

return scm;
}

/**
* Examines the indexId property, and converts it into a Map keyed by the values in INDEX_ID_MAPPING_OPTIONS
* @return A Map with the custom indexId definition
*/
private Map<String, Object> buildIndexIdMapping() {
if ((indexId != null) && !root) {
throw new IllegalArgumentException("'indexId' was used on non-root '" + grailsDomainClass.getPropertyName() + "#searchable': indexId may only apply to root searchable classes");
}

Object args = indexId;

Map indexIdMapping = null;
if (args instanceof String) {
indexIdMapping = Collections.singletonMap(INDEX_ID_PROPERTIES_NAME, Collections.singletonList(args));
}
else if (args instanceof Collection) {
indexIdMapping = Collections.singletonMap(INDEX_ID_PROPERTIES_NAME, new ArrayList<String>((Collection) args));
}
else if (args instanceof Map) {
indexIdMapping = new HashMap();
for (Object key : ((Map)args).keySet()) {
if (!INDEX_ID_MAPPING_OPTIONS.contains(key.toString())) {
throw new IllegalArgumentException("'" + key + "' is not a valid attribute for 'indexId'");
}
indexIdMapping.put(key.toString(), ((Map)args).get(key));
}
if (!indexIdMapping.containsKey(INDEX_ID_PROPERTIES_NAME)) {
throw new IllegalArgumentException("'indexId' must contain a '" + INDEX_ID_PROPERTIES_NAME + "' attribute");
}
}
else {
throw new IllegalArgumentException("Unsupported 'indexId' argument: " + args);
}

return indexIdMapping;
}

private Set<String> getInheritedProperties(GrailsDomainClass domainClass) {
// check which properties belong to this domain class ONLY
Set<String> inheritedProperties = new HashSet<String>();
Expand Down Expand Up @@ -213,6 +277,7 @@ public void buildHashMapMapping(LinkedHashMap map, GrailsDomainClass domainClass
// Support old searchable-plugin syntax ([only: ['category', 'title']] or [except: 'createdAt'])
only = map.containsKey("only") ? map.get("only") : null;
except = map.containsKey("except") ? map.get("except") : null;
indexId = map.containsKey("indexId") ? map.get("indexId") : null;
buildMappingFromOnlyExcept(domainClass, inheritedProperties);
}

Expand Down