Search code examples
indexingsolrnullpointerexceptionnestedsolrj

SOLRJ-6.0.0: Insertion of a bean object which associate list of bean object is giving null pointer exception


Employee Bean Class:

public class Employee2 {

    private String id;
    private String name;
    private String designation;
    private double salary;
    private double totalExperience;
//    private Address2 address2;
    private Collection<Technology2> technologies2;
    private String content_type = "employee2";

    public Employee2() {
        super();
    }

    public Employee2(String id, String name, String designation, double salary, double totalExperience,
            Collection<Technology2> technologies2) {
        super();
        this.id = id;
        this.name = name;
        this.designation = designation;
        this.salary = salary;
        this.totalExperience = totalExperience;
//        this.address2 = address2;
        this.technologies2 = technologies2;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    @Field (value = "id")
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    @Field (value = "name")
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the designation
     */
    public String getDesignation() {
        return designation;
    }

    /**
     * @param designation the designation to set
     */
    @Field (value = "designation_s")
    public void setDesignation(String designation) {
        this.designation = designation;
    }

    /**
     * @return the salary
     */
    public double getSalary() {
        return salary;
    }

    /**
     * @param salary the salary to set
     */
    @Field (value = "salary_d")
    public void setSalary(double salary) {
        this.salary = salary;
    }

    /**
     * @return the totalExperience
     */
    public double getTotalExperience() {
        return totalExperience;
    }

    /**
     * @param totalExperience the totalExperience to set
     */
    @Field (value = "totalExperience_d")
    public void setTotalExperience(double totalExperience) {
        this.totalExperience = totalExperience;
    }

//    /**
//     * @return the address2
//     */
//    public Address2 getAddress() {
//        return address2;
//    }
//
//    /**
//     * @param address2 the address2 to set
//     */
//    @Field (child = true)
//    public void setAddress(Address2 address2) {
//        this.address2 = address2;
//    }

    /**
     * @return the technologies2
     */
    public Collection<Technology2> getTechnologies2() {
        return technologies2;
    }

    /**
     * @param technologies2 the technologies2 to set
     */
    @Field (child = true)
    public void setTechnologies2(Collection<Technology2> technologies2) {
        this.technologies2 = technologies2;
    }

    /**
     * @return the content_type
     */
    public String getContent_type() {
        return content_type;
    }

    /**
     * @param content_type the content_type to set
     */
    @Field(value="content_type_t")
    public void setContent_type(String content_type) {
        this.content_type = content_type;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Employee2 [id=" + id + ", name=" + name + ", designation=" + designation + ", salary=" + salary +
                ", totalExperience=" + totalExperience + ", technologies2=" + this.getTechnologies(technologies2) +
                ", content_type=" + content_type + "]";
    }

    private String getTechnologies(Collection<Technology2> technologies2) {
        String strTechnologies = "[";
        for(Technology2 technology: technologies2) {
            strTechnologies = strTechnologies+technology.toString();
        }
        return strTechnologies+"]";
    }

}

Technology Bean Class:

public class Technology2 {

    private String id;
    private String name;
    private int experience;
    private boolean certified;
    private String content_type = "technology2";


    public Technology2() {
        super();
    }

    public Technology2(String id, String name, int experience, boolean certified) {
        super();
        this.id = id;
        this.name = name;
        this.experience = experience;
        this.certified = certified;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }
    /**
     * @param id the id to set
     */
    @Field(value="id")
    public void setId(String id) {
        this.id = id;
    }
    /**
     * @return the name
     */
    public String getName() {
        return name;
    }
    /**
     * @param name the name to set
     */
    @Field(value="name")
    public void setName(String name) {
        this.name = name;
    }
    /**
     * @return the experience
     */
    public int getExperience() {
        return experience;
    }
    /**
     * @param experience the experience to set
     */
    @Field(value="experience_i")
    public void setExperience(int experience) {
        this.experience = experience;
    }
    /**
     * @return the certified
     */
    public boolean getCertified() {
        return certified;
    }
    /**
     * @param certified the certified to set
     */
    @Field(value="certified_b")
    public void setCertified(boolean certified) {
        this.certified = certified;
    }
    /**
     * @return the content_type
     */
    public String getContent_type() {
        return content_type;
    }
    /**
     * @param content_type the content_type to set
     */
    @Field(value="content_type_t")
    public void setContent_type(String content_type) {
        this.content_type = content_type;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Technology2 [id=" + id + ", name=" + name + ", experience=" + experience + ", certified=" + certified +
                ", content_type=" + content_type + "]";
    }

Bean insertion method is working fine if employee bean has a nested Address Bean, however in our case employee bean has nested Collection of Technology Bean, it is causing exception by below line

UpdateResponse response = solrClient.addBean(bean);

Insertion method:

public <T> boolean insert (T bean) {
        try {
            UpdateResponse response = solrClient.addBean(bean);
            System.out.println("insert bean ElapsedTime: " + response.getElapsedTime());
            solrClient.commit();
            return true;
        } catch (IOException | SolrServerException e) {
            e.printStackTrace();
        }
        return false;
    }

Here it is returning null pointer exception, below is the toString value of Employee2

Employee2 [id=EE130S, name=Vulrp, designation=NjLtK, salary=127334.59626719051, totalExperience=49.989444163266164, technologies2=[Technology2 [id=0TE130S, name=uyIOFlh, experience=21, certified=true, content_type=technology2]Technology2 [id=1TE130S, name=FmZJjak, experience=43, certified=false, content_type=technology2]Technology2 [id=2TE130S, name=ddJbOXg, experience=11, certified=false, content_type=technology2]Technology2 [id=3TE130S, name=rIxumUe, experience=5, certified=true, content_type=technology2]], content_type=employee2]

It is causing below exception:

java.lang.NullPointerException
    at org.apache.solr.client.solrj.beans.DocumentObjectBinder$DocField.storeType(DocumentObjectBinder.java:243)
    at org.apache.solr.client.solrj.beans.DocumentObjectBinder$DocField.<init>(DocumentObjectBinder.java:183)
    at org.apache.solr.client.solrj.beans.DocumentObjectBinder.collectInfo(DocumentObjectBinder.java:144)
    at org.apache.solr.client.solrj.beans.DocumentObjectBinder.getDocFields(DocumentObjectBinder.java:123)
    at org.apache.solr.client.solrj.beans.DocumentObjectBinder.toSolrInputDocument(DocumentObjectBinder.java:76)
    at org.apache.solr.client.solrj.SolrClient.addBean(SolrClient.java:277)
    at org.apache.solr.client.solrj.SolrClient.addBean(SolrClient.java:259)
    at com.opteamix.buildpal.poc.SampleSolrDAO.insert(SampleSolrDAO.java:62)
    at com.opteamix.buildpal.poc.SampleSolrDAOTest.testEmployees2Insert(SampleSolrDAOTest.java:94)
    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 junit.framework.TestCase.runTest(TestCase.java:168)
    at junit.framework.TestCase.runBare(TestCase.java:134)
    at junit.framework.TestResult$1.protect(TestResult.java:110)
    at junit.framework.TestResult.runProtected(TestResult.java:128)
    at junit.framework.TestResult.run(TestResult.java:113)
    at junit.framework.TestCase.run(TestCase.java:124)
    at junit.framework.TestSuite.runTest(TestSuite.java:243)
    at junit.framework.TestSuite.run(TestSuite.java:238)
    at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Solution

  • Insertion of a bean object which associates list of bean is working as expected now.

    Finally after looking the solrj6.0.0 source code, I found the way to resolve it. Actually there is a bug in solrj6.0.0. That is: If we are giving @Field annotation at set method in Employee2 bean as below:

    /**
         * @param technologies2 the technologies2 to set
         */
        @Field (child = true)
        public void setTechnologies2(Collection<Technology2> technologies2) {
            this.technologies2 = technologies2;
        }
    

    Then it is causing exception for our Employee2 bean insertion which aggregates list of technologies. It seems to be a bug from sorlj code as:

    Nested DocField class of DocumentObjectBinder has below implemention:

    public DocField(AccessibleObject member) {
          if (member instanceof java.lang.reflect.Field) {
            field = (java.lang.reflect.Field) member;
          } else {
            setter = (Method) member;
          }
          annotation = member.getAnnotation(Field.class);
          storeName(annotation);
          storeType();
    
          // Look for a matching getter
          if (setter != null) {
            String gname = setter.getName();
            if (gname.startsWith("set")) {
              gname = "get" + gname.substring(3);
              try {
                getter = setter.getDeclaringClass().getMethod(gname, (Class[]) null);
              } catch (Exception ex) {
                // no getter -- don't worry about it...
                if (type == Boolean.class) {
                  gname = "is" + setter.getName().substring(3);
                  try {
                    getter = setter.getDeclaringClass().getMethod(gname, (Class[]) null);
                  } catch(Exception ex2) {
                    // no getter -- don't worry about it...
                  }
                }
              }
            }
          }
        }
    

    As we annotated @Field (child = true) at the setter hence here in this case field is null which is causing null pointer exception by storeType() method

    private void storeType() {
          if (field != null) {
            type = field.getType();
          } else {
            Class[] params = setter.getParameterTypes();
            if (params.length != 1) {
              throw new BindingException("Invalid setter method. Must have one and only one parameter");
            }
            type = params[0];
          }
    
          if (type == Collection.class || type == List.class || type == ArrayList.class) {
            isList = true;
            if (annotation.child()) {
              populateChild(field.getGenericType());
            } else {
              type = Object.class;
            }
          } else if (type == byte[].class) {
            //no op
          } else if (type.isArray()) {
            isArray = true;
            if (annotation.child()) {
              populateChild(type.getComponentType());
            } else {
              type = type.getComponentType();
            }
          } else if (type == Map.class || type == HashMap.class) { //corresponding to the support for dynamicFields
            if (annotation.child()) throw new BindingException("Map should is not a valid type for a child document");
            isContainedInMap = true;
            //assigned a default type
            type = Object.class;
            if (field != null) {
              if (field.getGenericType() instanceof ParameterizedType) {
                //check what are the generic values
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Type[] types = parameterizedType.getActualTypeArguments();
                if (types != null && types.length == 2 && types[0] == String.class) {
                  //the key should always be String
                  //Raw and primitive types
                  if (types[1] instanceof Class) {
                    //the value could be multivalued then it is a List, Collection, ArrayList
                    if (types[1] == Collection.class || types[1] == List.class || types[1] == ArrayList.class) {
                      type = Object.class;
                      isList = true;
                    } else {
                      //else assume it is a primitive and put in the source type itself
                      type = (Class) types[1];
                    }
                  } else if (types[1] instanceof ParameterizedType) { //Of all the Parameterized types, only List is supported
                    Type rawType = ((ParameterizedType) types[1]).getRawType();
                    if (rawType == Collection.class || rawType == List.class || rawType == ArrayList.class) {
                      type = Object.class;
                      isList = true;
                    }
                  } else if (types[1] instanceof GenericArrayType) { //Array types
                    type = (Class) ((GenericArrayType) types[1]).getGenericComponentType();
                    isArray = true;
                  } else { //Throw an Exception if types are not known
                    throw new BindingException("Allowed type for values of mapping a dynamicField are : " +
                        "Object, Object[] and List");
                  }
                }
              }
            }
          } else {
            if (annotation.child()) {
              populateChild(type);
            }
          }
        }
    

    So as of now I am annotating @Field annotation at field level rather than at setter:

     @Field (child = true)
        private Collection<Technology2> technologies2;
    

    So now insertion of such bean is successful, on retrieving I am getting below result as expected:

    Employee2 [id=E3, name=KzWhg, designation=aTDiu, salary=190374.70126209356, totalExperience=2.0293696897450584, technologies2=[Technology2 [id=0T3, name=nxTdufv, experience=46, certified=false, content_type=technology2]Technology2 [id=1T3, name=waSMXpf, experience=26, certified=false, content_type=technology2]Technology2 [id=2T3, name=jqNbZZr, experience=30, certified=true, content_type=technology2]Technology2 [id=3T3, name=VnidjyI, experience=21, certified=true, content_type=technology2]Technology2 [id=4T3, name=ulGnHFm, experience=33, certified=false, content_type=technology2]Technology2 [id=5T3, name=cpUfgrY, experience=21, certified=false, content_type=technology2]], content_type=employee2] Employee2 [id=E4, name=xeKOY, designation=WfPSm, salary=169700.53869292728, totalExperience=22.047282596410284, technologies2=[Technology2 [id=0T4, name=rleygcW, experience=30, certified=true, content_type=technology2]Technology2 [id=1T4, name=yxjHrxV, experience=27, certified=false, content_type=technology2]Technology2 [id=2T4, name=czjHAEE, experience=31, certified=false, content_type=technology2]Technology2 [id=3T4, name=RDhoIJw, experience=22, certified=false, content_type=technology2]Technology2 [id=4T4, name=UkbldDN, experience=19, certified=false, content_type=technology2]], content_type=employee2] Employee2 [id=E5, name=tIWuY, designation=WikuL, salary=41462.47225086359, totalExperience=13.407976384902403, technologies2=[Technology2 [id=0T5, name=CDCMunq, experience=6, certified=false, content_type=technology2]Technology2 [id=1T5, name=NmkADyB, experience=31, certified=false, content_type=technology2]Technology2 [id=2T5, name=IhXnLfc, experience=9, certified=true, content_type=technology2]], content_type=employee2] Employee2 [id=E6, name=YluDp, designation=EtFqG, salary=159724.66206009954, totalExperience=26.26819742766281, technologies2=[Technology2 [id=0T6, name=mFvKDIK, experience=33, certified=false, content_type=technology2]Technology2 [id=1T6, name=arTNoHj, experience=44, certified=true, content_type=technology2]Technology2 [id=2T6, name=KYMseTW, experience=34, certified=false, content_type=technology2]Technology2 [id=3T6, name=ZTphSVn, experience=13, certified=true, content_type=technology2]], content_type=employee2] Employee2 [id=E7, name=qMkKG, designation=SQHCo, salary=111861.53447042785, totalExperience=13.29234679211927, technologies2=[Technology2 [id=0T7, name=PTKxjFl, experience=23, certified=false, content_type=technology2]Technology2 [id=1T7, name=gJfxbto, experience=17, certified=true, content_type=technology2]Technology2 [id=2T7, name=eekPYPN, experience=40, certified=true, content_type=technology2]Technology2 [id=3T7, name=aRdsEag, experience=40, certified=true, content_type=technology2]Technology2 [id=4T7, name=loDFVyM, experience=40, certified=true, content_type=technology2]Technology2 [id=5T7, name=xPXNaDV, experience=0, certified=false, content_type=technology2]], content_type=employee2] Employee2 [id=E8, name=WyNsf, designation=TtanH, salary=107942.13641940584, totalExperience=47.036469485140984, technologies2=[Technology2 [id=0T8, name=kakGXqh, experience=14, certified=true, content_type=technology2]Technology2 [id=1T8, name=ugwgdHy, experience=9, certified=true, content_type=technology2]Technology2 [id=2T8, name=rNzwcdQ, experience=31, certified=false, content_type=technology2]Technology2 [id=3T8, name=ZBXUhuB, experience=6, certified=true, content_type=technology2]], content_type=employee2] Employee2 [id=E9, name=EzuLC, designation=IXYGj, salary=133064.4485190016, totalExperience=16.075378097234232, technologies2=[Technology2 [id=0T9, name=GmvOUWp, experience=5, certified=true, content_type=technology2]Technology2 [id=1T9, name=ZWyvRxk, experience=24, certified=false, content_type=technology2]Technology2 [id=2T9, name=uWkTrfB, experience=5, certified=false, content_type=technology2]Technology2 [id=3T9, name=NFknqJj, experience=29, certified=true, content_type=technology2]], content_type=employee2] Employee2 [id=E10, name=quFKB, designation=eUoBJ, salary=198332.3270496455, totalExperience=14.035578311712438, technologies2=[Technology2 [id=0T10, name=MOXduwi, experience=49, certified=false, content_type=technology2]Technology2 [id=1T10, name=LpXGRvn, experience=28, certified=false, content_type=technology2]Technology2 [id=2T10, name=QeAOjIp, experience=3, certified=true, content_type=technology2]Technology2 [id=3T10, name=aVxGhOV, experience=34, certified=true, content_type=technology2]Technology2 [id=4T10, name=fbSaBUm, experience=42, certified=true, content_type=technology2]], content_type=employee2]

    I have raised the code defect in JIRA: https://issues.apache.org/jira/browse/SOLR-9112