package com.mwmd.aem.search.solr.impl;
import com.mwmd.aem.search.core.indexing.IndexException;
import com.mwmd.aem.search.core.indexing.IndexServer;
import com.mwmd.aem.search.core.indexing.ResourceBinary;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrServer;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrInputDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Solr implementation of {@link IndexServer}. This is the core of the EASE Solr connector.<br/>
* The connector requires an OSGi configuration in order to work. The following properties need to be configured:
* <ul>
* <li><i>url</i>: Address of the Solr instance. This must include the collection of the index within the Solr root
* path.</li>
* <li><i>id_prefix</i>: Prefix to put in front of all generated identifiers. The identifier will consist of the content
* path with the configured prefix.</li>
* <li><i>id_field</i>: Name of the unique identifier field in the Solr schema. Solr requires this field to be
* filled.</li>
* </ul>
*
* @author Matthias Wermund
*/
@Service
@Properties({
@Property(name = "url", label = "Solr URL", description = "Address of Solr instance where to send update requests to."),
@Property(name = "id_prefix", label = "ID prefix", description = "Prefix for path to generate unique ID.", value = "AEM:"),
@Property(name = "id_field", label = "ID field name", description = "Field name of ID field.", value = "id")
})
@Component(policy = ConfigurationPolicy.REQUIRE)
public class SolrIndexServer implements IndexServer {
private static final Logger LOG = LoggerFactory.getLogger(SolrIndexServer.class);
private static final DateFormat FORMAT_TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
private static final NumberFormat FORMAT_NUMBER = NumberFormat.getInstance();
static {
FORMAT_NUMBER.setGroupingUsed(false);
FORMAT_NUMBER.setMinimumFractionDigits(0);
}
private ConcurrentUpdateSolrServer solrServer;
private String idPrefix;
private String idField;
@Activate
protected void activate(Map<String, Object> properties) {
String url = (String) properties.get("url");
if (url != null) {
this.solrServer = new ConcurrentUpdateSolrServer(url, 25, 2);
try {
this.solrServer.commit();
} catch (Exception e) {
LOG.error("Error creating connection to index server.", e);
}
}
this.idPrefix = (String) properties.get("id_prefix");
this.idField = (String) properties.get("id_field");
}
@Deactivate
protected void deactivate() {
if (this.solrServer != null) {
this.solrServer.shutdown();
}
}
@Override
public void add(String path, Map<String, Object> data) throws IndexException {
SolrInputDocument document = new SolrInputDocument();
document.addField(this.idField, buildId(path));
Set<String> fields = data.keySet();
for (String field : fields) {
if (field.equals(this.idField)) {
throw new IndexException("ID field must not get populated through data fields.");
}
Object fieldValue = data.get(field);
if (fieldValue != null) {
// should work with both Collection and single values
document.addField(field, fieldValue);
}
}
try {
solrServer.add(document);
} catch (Exception e) {
throw new IndexException(e);
}
}
@Override
public void add(String path, Map<String, Object> data, ResourceBinary binary) throws IndexException {
if (binary == null) {
add(path, data);
return;
}
ContentStreamUpdateRequest request = new ContentStreamUpdateRequest("/update/extract");
request.addContentStream(new AssetContentStream(binary));
request.setParam("literal.id", buildId(path));
Set<String> fields = data.keySet();
for (String field : fields) {
if (field.equals(this.idField)) {
throw new IndexException("ID field must not get populated through data fields.");
}
Object fieldValue = data.get(field);
if (fieldValue != null) {
if (fieldValue instanceof Collection) {
Collection<?> fieldValues = (Collection) fieldValue;
for (Object value : fieldValues) {
// TODO deal with numeric & date type values (format to string)
request.setParam("literal." + field, valueToString(value));
}
} else {
// assume it's one of the supported data types
request.setParam("literal." + field, valueToString(fieldValue));
}
}
}
request.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
try {
solrServer.request(request);
} catch (Exception e) {
throw new IndexException(e);
}
}
private static String valueToString(Object value) {
if (value instanceof Date) {
return FORMAT_TIMESTAMP.format(value);
}
if (value instanceof Calendar) {
return FORMAT_TIMESTAMP.format(((Calendar) value).getTime());
}
if (value instanceof Number) {
return FORMAT_NUMBER.format(value);
}
return value.toString();
}
private String buildId(String path) {
return StringUtils.defaultIfEmpty(this.idPrefix, "AEM:") + path;
}
@Override
public void remove(String path) throws IndexException {
try {
solrServer.deleteById(buildId(path));
} catch (Exception e) {
throw new IndexException(e);
}
}
@Override
public void commit() throws IndexException {
try {
solrServer.commit();
} catch (Exception e) {
throw new IndexException(e);
}
}
@Override
public void rollback() throws IndexException {
try {
solrServer.rollback();
} catch (Exception e) {
throw new IndexException(e);
}
}
@Override
public void clear() throws IndexException {
try {
UpdateRequest request = new UpdateRequest("/update");
request.deleteByQuery(this.idField + ":" + ClientUtils.escapeQueryChars(this.idPrefix) + "*");
solrServer.request(request);
solrServer.commit();
} catch (Exception e) {
throw new IndexException(e);
}
}
}