Spring, Riak and Solr Integration

Spring

References


Maven

pom.xml


<dependency>
    <groupId>com.basho.riak</groupId>
    <artifactId>riak-client</artifactId>
    <version>1.4.4</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </exclusion>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

  • The exclusions are in place to avoid dependency tree conflicts

applicationContext.xml

 <!-- Ignore Controllers -->
 <context:component-scan base-package="com.ntg.sme">
  <context:exclude-filter expression="org.springframework.stereotype.Controller"
   type="annotation" />
 </context:component-scan>

<context:property-placeholder location="classpath:riak.properties" />
<bean id="riakClient" class="com.basho.riak.client.RiakFactory"
    factory-method="httpClient">
    <constructor-arg type="java.lang.String" value="${${environment}.riak.url}/riak" />
</bean>
<solr:repositories base-package="com.ntg.sme.webapp.repository"
    multicore-support="true" />
<bean id="solrServer"
    class="org.springframework.data.solr.server.support.HttpSolrServerFactoryBean">
    <property name="url" value="${${environment}.riak.url}/solr" />
</bean>
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
    <constructor-arg ref="solrServer" />
</bean>

  • The Riak URL is read from a properties file
  • /riak and /solr need to be appended to the the Riak and Solr URLs respectively
  • In this case, an HttpClient is being used in place of PbcClient (load-balancer with Basic Authentication)
  • The timeout property of the solr-server object was causing connection timeout issues (bizarrely a value of null works) so an HttpSolrServerFactoryBean is used instead
  • In order to use multiple buckets (cores) for searching, set the multicore-support attribute to true

riak.properties

dev.riak.url=http://riak.ntg.sme.com
production.riak.url=http://riak.ntg.sme.com

  • Add -Denvironment=dev/production as a startup parameter

Solr

XML

  • Ensure that XML is parsed rather than the Java binary format (javabin):
@Override
public void afterPropertiesSet() throws Exception {
    ((HttpSolrServer) solrServer).setParser(new SmeResponseParser());
}

  • Orginally this was XMLResponseParser but there was a problem with HttpSolrServer checking the content-type of the response (application/xml != text/xml)
  • An SmeResponseParser was created to return text/xml:
import org.apache.solr.client.solrj.impl.XMLResponseParser;
public class SmeResponseParser extends XMLResponseParser {
    public static final String XML_CONTENT_TYPE = "text/xml; charset=UTF-8";
    public String getContentType() {
        return XML_CONTENT_TYPE;
    }
}

Query

SolrQuery query = new SolrQuery();
query.set("q", "identification_tradingAddress_postcode:WR7*");
QueryResponse rsp = solrServer.query(query);

Transform

  • Convert the XML into an object:
List list = solrTemplate.convertQueryResponseToBeans(rsp, Compan.class);

Domain

  • No nested structures (i.e. flat)
  • Annotate fields with @Field if the field name differs from the XML
  • Add a value to the @Field to map to the XML response; especially important for nested objects: @Field("identification_roAddress_add1")
  • Add @Id to the identifier (unless the field is called Id, in which case no annotations are required
  • Add @RiakKey to the identifier if the object is being saved in Riak
  • Add @SolrDocument to identify the index (core) to use
@SolrDocument(solrCoreName = "company")
public class CompanySearch implements Serializable {
 
    @Id
    @Field("id")
    private String company;
    @Field("identification_alpha")
    private String alpha;
    @Field("identification_name")
    private String name;
    @Field("identification_roAddress_add1")
    private String add1;

Logging

Maven

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>       
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j.properties

log4j.rootLogger=DEBUG, solrLog
log4j.appender.solrLog=org.apache.log4j.ConsoleAppender
log4j.appender.solrLog.layout=org.apache.log4j.PatternLayout
log4j.appender.solrLog.layout.ConversionPattern=%-4r [%t] %-5p %c %x – %m%n

Dates

  • Ensure dates are stored in a consistent manner for Solr and Riak. The @JsonFormat annotation ensures that the date is written in this format by jackson (required for InterConnect):

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
private Date transaction_date;
  • Specify the gson date format within InterConnect:
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create();
  • Specify a global SimpleDateFormat (ISO 8601). This specifies that all dates are read and written in the format specified within a Spring web application:
<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean
            class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="com.knappsack.swagger4springweb.util.ScalaObjectMapper">
                    <property name="serializationInclusion">
                        <value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
                    </property>
                    <property name="dateFormat">
                        <bean class="java.text.SimpleDateFormat" p:timeZone="GMT">
                            <constructor-arg value="yyyy-MM-dd'T'HH:mm:ssZ" />
                            <constructor-arg>
                                <bean class="java.util.Locale">
                                    <constructor-arg index="0" value="en" />
                                    <constructor-arg index="1" value="US" />
                                </bean>
                            </constructor-arg>
                        </bean>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Repositories

public interface CompanyRepository extends Repository<Company, String> {
    List findByNameLike(String name);
}

No comments:

Post a Comment