1

I am playing around with Spring Data Solr with Schemaless Solr. There are some dots I am not able to connect: when and how are the schema fields being created?

The following page states

Automatic schema population will inspect your domain types whenever the applications context is refreshed and populate new fields to your index based on the properties configuration. This requires solr to run in Schemaless Mode.

Use @Indexed to provide additional details like specific solr types to use.

It also goes to show a curl request:

// curl ../solr/collection1/schema/fields -X POST -H 'Content-type:application/json'

However, when I run a simple example with field annotated with @Indexed, I do not see the /schema/fields API being called on SOLR. How are these fields created?

The reason I am asking is that they seem to be automatically created with multiValued=true. I did not see the @Indexed annotation taking multiValued as a parameter. How can I force Spring Data Solr to declare fields as non-multiValued when it creates them?

Now, all of this is really to resolve this exception I am seeing.

java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.springframework.util.Assert.notNull(Assert.java:126)
at org.springframework.data.solr.core.convert.MappingSolrConverter$SolrPropertyValueProvider.readValue(MappingSolrConverter.java:426)
at org.springframework.data.solr.core.convert.MappingSolrConverter$SolrPropertyValueProvider.readCollection(MappingSolrConverter.java:601)
at org.springframework.data.solr.core.convert.MappingSolrConverter$SolrPropertyValueProvider.readValue(MappingSolrConverter.java:440)
at org.springframework.data.solr.core.convert.MappingSolrConverter$SolrPropertyValueProvider.readValue(MappingSolrConverter.java:412)
at org.springframework.data.solr.core.convert.MappingSolrConverter$SolrPropertyValueProvider.getPropertyValue(MappingSolrConverter.java:395)
at org.springframework.data.solr.core.convert.MappingSolrConverter.getValue(MappingSolrConverter.java:206)
at org.springframework.data.solr.core.convert.MappingSolrConverter$1.doWithPersistentProperty(MappingSolrConverter.java:194)
at org.springframework.data.solr.core.convert.MappingSolrConverter$1.doWithPersistentProperty(MappingSolrConverter.java:186)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:309)
at org.springframework.data.solr.core.convert.MappingSolrConverter.read(MappingSolrConverter.java:186)
at org.springframework.data.solr.core.convert.MappingSolrConverter.read(MappingSolrConverter.java:174)
at org.springframework.data.solr.core.convert.MappingSolrConverter.read(MappingSolrConverter.java:149)
at org.springframework.data.solr.core.SolrTemplate.convertSolrDocumentListToBeans(SolrTemplate.java:560)
at org.springframework.data.solr.core.SolrTemplate.convertQueryResponseToBeans(SolrTemplate.java:552)
at org.springframework.data.solr.core.SolrTemplate.createSolrResultPage(SolrTemplate.java:369)
at org.springframework.data.solr.core.SolrTemplate.doQueryForPage(SolrTemplate.java:300)
at org.springframework.data.solr.core.SolrTemplate.queryForPage(SolrTemplate.java:308)
at org.springframework.data.solr.repository.support.SimpleSolrRepository.findAll(SimpleSolrRepository.java:111)
at org.springframework.data.solr.repository.support.SimpleSolrRepository.findAll(SimpleSolrRepository.java:106)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at 

My guess is that the exception is happening because values are being returned as a collection?

I tried to step through the code to understand what is happening. The field that is causing problem is "name", the value is [product-1]. The exception is happening when trying to unmarshall a solr document into a POJO.

First we go inside the following method:

    private <T> T readValue(Object value, TypeInformation<?> type, Object parent) {
        if (value == null) {
            return null;
        }

        Assert.notNull(type);
        Class<?> rawType = type.getType();
        if (hasCustomReadTarget(value.getClass(), rawType)) {
            return (T) convert(value, rawType);
        }

        Object documentValue = null;
        if (value instanceof SolrInputField) {
            documentValue = ((SolrInputField) value).getValue();
        } else {
            documentValue = value;
        }

        if (documentValue instanceof Collection) {
            return (T) readCollection((Collection<?>) documentValue, type, parent);
        } else if (canConvert(documentValue.getClass(), rawType)) {
            return (T) convert(documentValue, rawType);
        }

        return (T) documentValue;

    }

When calling this method, the value is a collection and the type is java.lang.String. This results in the if(documentValue instanceof Collection) being selected, which results into following method being executed:

        private Object readCollection(Collection<?> source, TypeInformation<?> type, Object parent) {
        Assert.notNull(type);

        Class<?> collectionType = type.getType();
        if (CollectionUtils.isEmpty(source)) {
            return source;
        }

        collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;

        Collection<Object> items;
        if (type.getType().isArray()) {
            items = new ArrayList<Object>();
        } else {
            items = CollectionFactory.createCollection(collectionType, source.size());
        }

        TypeInformation<?> componentType = type.getComponentType();

        Iterator<?> it = source.iterator();
        while (it.hasNext()) {
            items.add(readValue(it.next(), componentType, parent));
        }

        return type.getType().isArray() ? convertItemsToArrayOfType(type, items) : items;
    }

In this method, we end up calling type.getComponentType(), which returns null and ultimately cause Assert.notNull() to fail.

What am I missing in all this?

My code is as follows. Launch and config class:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableSolrRepositories(schemaCreationSupport=true, basePackages = { "com.example.solrdata" }, multicoreSupport = true)
public class Application {

public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}
}

Model class:

@SolrDocument(solrCoreName = "collection1")
public class Product {

@Id
String id;
@Indexed
String name;

public Product(String id, String name) {
    this.id = id;
    this.name = name;
}

public Product() {
    super();
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}}

Repository:

public interface ProductRepository extends SolrCrudRepository<Product, String> {}

Test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class SolrProductRepositoryTest  {

@Autowired
private ProductRepository repo;

@Before @After
public void setup(){
    repo.deleteAll();
}

@Test
public void testCRUD() {
    assertEquals(0, repo.count());
    Product product = new Product("1","product-1");
    repo.save(product);
    assertEquals(1, repo.count());
    Product product2 = repo.findOne(product.getId());
    assertEquals(product2.getName(), product.getName());
}}

And finally, my POM:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.3.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-solr</artifactId>
    </dependency>
</dependencies>
Community
  • 1
  • 1
Klaus
  • 2,328
  • 5
  • 41
  • 62
  • sd-solr will only create new fields (multivalued in case the property is collection like) when those do not already exists and it requires the server to run in schema less mode. Still there's a schema present on the server. Can you add that one please. – Christoph Strobl Mar 11 '16 at 07:42
  • Do you mean I should add a schema? I am running solr in schemaless mode and this is one of my requirements. So, adding a schema is not the preferred route. Now, if the multivalued=true behavior is hardcoded, how do I avoid the above exception? – Klaus Mar 11 '16 at 11:09
  • though running solr in schemaless mode does not mean there is no schema. it is there and might have already been initialized with defaults. just have a look into the conf directory and open the file `managed-schema` though no _xml_ file it contains your schema information – Christoph Strobl Mar 11 '16 at 11:57
  • I took a brand new solr deployment, unzipped from solr-4.10.4.zip, verified that there was no managed-schema, started solr in schemaless mode, and ran the above test. As a result, managed-schema is generated and the above exception is thrown. – Klaus Mar 11 '16 at 12:15

1 Answers1

0

When using Solr 4.10 with the defaults unfortunately the JSONResponseWriter in solrconfig.xml uses text/pain as content type.

<queryResponseWriter name="json" class="solr.JSONResponseWriter">
  <str name="content-type">text/plain; charset=UTF-8</str>
</queryResponseWriter>

This causes the content type negotiation of SolrSchemaRequest to silently fail and skip the schema update step - and solr default field type guessing kicks in at that place.

Setting content-type to application/json allows to add fields according to the bean definition.

@SolrDocument(solrCoreName = "collection1")
public static class SomeDomainType {

    @Indexed @Id 
    String id;

    @Indexed 
    String indexedStringWithoutType;

    @Indexed(name = "namedField", type = "string", searchable = false) 
    String justAStoredField;

    @Indexed 
    List<String> listField;

    @Indexed(type = "tdouble") 
    Double someDoubleValue;
}

Before

{
    responseHeader: {
        status: 0,
        QTime: 86
    },
    fields: [
        {
            name: "_version_",
            type: "long",
            indexed: true,
            stored: true
        },
        {
            name: "id",
            type: "string",
            multiValued: false,
            indexed: true,
            required: true,
            stored: true,
            uniqueKey: true
        }
    ]
}

After Schema Upate

{
    responseHeader: {
        status: 0,
        QTime: 1
    },
    fields: [
        {
            name: "_version_",
            type: "long",
            indexed: true,
            stored: true
        },
        {
            name: "id",
            type: "string",
            multiValued: false,
            indexed: true,
            required: true,
            stored: true,
            uniqueKey: true
        },
        {
            name: "indexedStringWithoutType",
            type: "string",
            multiValued: false,
            indexed: true,
            required: false,
            stored: true
        },
        {
            name: "listField",
            type: "string",
            multiValued: true,
            indexed: true,
            required: false,
            stored: true
        },
        {
            name: "namedField",
            type: "string",
            multiValued: false,
            indexed: false,
            required: false,
            stored: true
        },
        {
            name: "someDoubleValue",
            type: "tdouble",
            multiValued: false,
            indexed: true,
            required: false,
            stored: true
        }
    ]
}   
Christoph Strobl
  • 6,491
  • 25
  • 33
  • Thanks for the update Christoph. It doesn't seem to work for me. Brand new solr-4.10.4. The following solrconfig.xml changed: ./solr-4.10.4/example/example-schemaless/solr/collection1/conf/solrconfig.xml. I launch solr with "bin/solr -e schemaless". JSONresponseWriter is configured with: application/json; charset=UTF-8 Exception is the same. – Klaus Mar 14 '16 at 16:52
  • do you have a running sample with testcases I can run? Mine pass with the canges to solrconfig.xml outlined above. – Christoph Strobl Mar 14 '16 at 18:17
  • I uploaded my sample project (with test case) at: http://expirebox.com/download/cdf6def72b988528f0d2818047ff559d.html. It is basically made up of the pieces I pasted above. – Klaus Mar 14 '16 at 19:40
  • Any idea? Should I open a ticket? – Klaus Mar 18 '16 at 04:14