1

I'm using Spring (boot) 2.2.7 with mongoDB 4.0 running on Debian Stretch (9.12). I've setup 3 collections that I'm trying to join via an aggregation lookup cascading operation.

  • operations (Node class)
  • stock (Component class)
  • catalog (Product class)

Every class has an uuid string property that links them together.

Node (sensors, sinks, base stations ... etc)

@Document(collection = "operations")
public class Node implements Serializable {
    @Id
    private String _id;
    private String componentUuId;
....
}

Component (a delivered product with serial number, barecode ...)

@Document(collection = "stock")
public class Component implements Serializable {
    @Id
    private String _id;
    private String uuId;
    private String productUuId;
....
}

Product (a market artefact)

@Document(collection = "catalog")
public class Product implements Serializable {
    @Id
    private String _id;
    private String uuId;
....
}

when running this command in the mongo shell :

db.operations.aggregate([
   {
     $lookup: {
         "from": "stock",
         "localField": "componentUuId",
         "foreignField": "uuId",
         "as": "component"
     }
   },
   {
     $lookup: {
         "from": "catalog",
         "localField": "component.productUuId",
         "foreignField": "uuId",
         "as": "product"
     }
   }
]).pretty()

I get

{
    "_id" : ObjectId("5ec952a7714e7e248b5fb98b"),
    "uuId" : "3b5e3417-166c-4d69-9fa5-b0856d819541",
    "componentUuId" : "57eff5c5-8b3c-43cb-9f2d-1c4098fc0c1f",
    "component" : [
        {
            "_id" : ObjectId("5ec952a4714e7e248b5fb97f"),
            "uuId" : "57eff5c5-8b3c-43cb-9f2d-1c4098fc0c1f",
            "productUuId" : "3da7a6a6-ccf6-4fe1-892a-a6d6b82396c1",
            "created" : ISODate("2020-05-23T14:43:16.588Z"),
            "_class" : "org.open_si.udc_common.models.Component"
            .......
        }
    ],
    "product" : [
        {
            "_id" : ObjectId("5ec952a1714e7e248b5fb969"),
            "uuId" : "3da7a6a6-ccf6-4fe1-892a-a6d6b82396c1",
            "model" : "TMP36GT9Z",
            "_class" : "org.open_si.udc_common.models.Product"
            ......
        }
    ]
}

so to reach the same goal in Java (w/ Spring-data-mongodb) I ran

Aggregation agg = Aggregation.newAggregation(
    Aggregation.lookup(mongoTemplate.getCollectionName(Component.class),
        "componentUuId", "uuId", "component"),
    Aggregation.lookup(mongoTemplate.getCollectionName(Product.class),
        "component.productUuId", "uuId", "product")
);

AggregationResults<NodeExpanded> results = mongoTemplate.aggregate(agg, mongoTemplate.getCollectionName(Node.class), NodeExpanded.class);

return results.getMappedResults();

using the NodeExpanded class

public class NodeExpanded implements Serializable {

    private String uuId;
    private Component component;
    private Product product;
....
}

note : I don't extend the Node base class as I get a duplicated key error before the following issue.

the problem is that I face an error in the service hosting that code :

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null

unfortunately I don't know how to debug at lower level in Spring and can't find out what's the original error.

Note that the code bellow to get a Product linked to a Component works fine :

Aggregation agg = Aggregation.newAggregation(
                Aggregation.lookup(mongoTemplate.getCollectionName(Product.class),
                        "productUuId", "uuId", "product")
        );

AggregationResults<ComponentExpanded> results =
                mongoTemplate.aggregate(agg,mongoTemplate.getCollectionName(Component.class), ComponentExpanded.class);
return results.getMappedResults();

with the expanded class :

public class ComponentExpanded implements Serializable {
    private String uuId;
    private Product product;
...
}

any idea ? thanks in advance.


EDIT

trying to debug deeper, I've restricted the code to only join the operations collection(Node.class) to the stock one (Component.class) in the same way I've done with the stock (Component.class) and catalog (Product.class) that works fine.

public List<NodeExpanded> list() {

    Aggregation agg = Aggregation.newAggregation(
            Aggregation.lookup(mongoTemplate.getCollectionName(Component.class),
                    "componentUuId", "uuId", "component")

    );

    AggregationResults<NodeExpanded> results = mongoTemplate.aggregate(agg, mongoTemplate.getCollectionName(Node.class), NodeExpanded.class);
    return results.getMappedResults();
}

with a simple NodeExpanded class as

public class NodeExpanded implements Serializable {

    private String uuId;
    private Component component;

}

and I get the same null pointer exception on the mongoTemplate.aggregate() instruction


SOLVED

The problem comes form the SensorPersistenceService that doesn't @Autowired the MongoTemplate. With the informations given in that post it was not possible to find out the error cause. Thanks again to @Valijon for his help during this quest ^^

Emmanuel BRUNET
  • 1,286
  • 3
  • 19
  • 46
  • Seems you are initializing wrongly `SensorPersistenceService`. Try to put everywhere for attributes ending `..Service`, `Repository` and `@Autowired` annotation. – Valijon May 25 '20 at 20:32
  • Check this https://stackoverflow.com/questions/21282919/spring-3-request-processing-failed-nested-exception-is-java-lang-nullpointerexc – Valijon May 25 '20 at 20:51
  • Gonna try. I don't autowire services as they are injected in the controllers contructors. Only repositories are. – Emmanuel BRUNET May 26 '20 at 10:00
  • You were right, not about app services / repositories but with the MongoTemplate that was not @Autowired in the SensorPersistenceService. – Emmanuel BRUNET May 26 '20 at 10:18

1 Answers1

0

Probably the problem is NodeExpanded expects single Component and Product, but $lookup aggregation returns an array for both.

If you add extra $project stage, it should solve your problem.

Aggregation agg = Aggregation.newAggregation(
    Aggregation.lookup(mongoTemplate.getCollectionName(Component.class), 
        "componentUuId", "uuId", "component"),
    Aggregation.lookup(mongoTemplate.getCollectionName(Product.class), 
        "component.productUuId", "uuId", "product"),
    Aggregation.project()
        .andInclude("uuId") //put here all fields for NodeExpanded
        .and(ArrayOperators.ArrayElemAt.arrayOf("component").elementAt(0)).as("component")
        .and(ArrayOperators.ArrayElemAt.arrayOf("product").elementAt(0)).as("product")
);
Valijon
  • 12,667
  • 4
  • 34
  • 67
  • thanks for your help again @Valijon. Unfortunately I don't think that's the original problem. I've tested a very simple code (see my post edit) , and it still results in a null pointer exception. – Emmanuel BRUNET May 25 '20 at 13:11
  • @EmmanuelBRUNET can you share full code / data? It's impossible to work fine when `$lookup` returns an array, but your classes has single field value. – Valijon May 25 '20 at 13:29
  • sure ... common classes are located in the [common] project and the frontend controllers in the [gateway] projet. As I'am newby feel free to give me advices, thanks in advance. You'll find the whole code at https://gitlab.open-si.org (account: guest - pwd : grenoble2020) – Emmanuel BRUNET May 25 '20 at 14:09
  • Note : the mongoDb data can be loaded using the simulator project (see simulator / wiki / usage) – Emmanuel BRUNET May 25 '20 at 14:15
  • As I've some parsing problem when exporting data from mongoDb , there's a basic database configuration data in the simulator project / wiki / Database configuration collections example – Emmanuel BRUNET May 25 '20 at 14:42
  • @EmmanuelBRUNET Guide me please. Where can I find your posted code (which gives nullpointer) inside https://gitlab.open-si.org/udc/common/-/tree/master/src/main/java/org/open_si/udc_common ? – Valijon May 25 '20 at 14:57
  • in the SensorPersistenceService.listExpanded() method line 96 (common project). Note : the StockPersistenceService.listExpanded() method line 86 that is very similar works fine. – Emmanuel BRUNET May 25 '20 at 15:13
  • in the services package – Emmanuel BRUNET May 25 '20 at 15:22
  • to facilitate testing cloning and building the common project (mvn clean install) then running the gateway app (via intellij for ex) should work as I've changed the mongodb configuration in the application.properties to allow remote access on mine that contains a reduced set of data – Emmanuel BRUNET May 25 '20 at 16:43
  • FYI few days ago I was using @DBref to do the job, it works but I didn't find out how to select / sort the result set on sub object properties like Product.model from the Component repository as it is only aware of the joining key but not of the whole object structure – Emmanuel BRUNET May 25 '20 at 16:52
  • @EmmanuelBRUNET Can I see your dataset? I am debugging piece of problematic code and it works correctly – Valijon May 25 '20 at 20:30
  • as noticed in my previous comment using the last MongoDb connexion parameters defined the gateway project / application.properties you should be able to access my testing database. Feel free to contact me if you face any connexion issue. – Emmanuel BRUNET May 26 '20 at 09:57