Home > OS >  Spring Webflux: Extract a value from a Mono and save it into another variable
Spring Webflux: Extract a value from a Mono and save it into another variable

Time:03-08

The stack used in the API is: Spring-boot, Spring Webflux, Hibernate Reactive 1.1.3 and Postgresql.

I'd like to extract a string value from a Mono called desiredField and add it to a hashmap. I'm showing the DB, model, repository and the util module where all this is happening.

The JWTUtil is used to generate a JWT token for login purposes in the API. The JWTController is working fine, coming the token in response! The only thing is that a cannot extract de string value from a Mono.

I've tried two approaches. The reactive way, using the subscribe method. And the blocking way using the block method. These two approches are shown below with their own console prints.

In the first approach (the reactive way) I couldn't extract the desiredField from the Mono. The token comes in the response but without the desiredField in the token claims. The desiredField live only inside de stream (see console log prints), outside is null.

In the second approach (blocking way) there are an infinite loop error without getting the token in the response.

Could you help me to extract this value from the Mono?

Thanks in advance!

Postgres:

############
# my_table #
############

id  |  desired_column
-------------------------------
 1  |  "I am the desired value"

Model:

@Data
@Entity(name = "MyModel")
@Table(name = "my_table")
public class MyModel{
    @Id
    @GeneratedValue
    private long id;

    @Column(name="desired_column")
    private String desiredField;
}

Repository:

@Slf4j
@Repository
public class MyModelRepository {

    public Mono<MyModel> getMyModelInstance() {
        try{
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistance");
            Mutiny.SessionFactory factory = emf.unwrap(Mutiny.SessionFactory.class);

            CriteriaBuilder builder = factory.getCriteriaBuilder();
            CriteriaQuery<MyModel> criteria = builder.createQuery(MyModel.class);
            Root<MyModel> root = criteria.from(MyModel.class);
            criteria.where(builder.equal(root.get("id"), "1"));

            Uni<MyModel> myModelInstanceUni= factory.withSession(
                    session -> session
                            .createQuery(criteria)
                            .getSingleResult()
            );

            return myModelInstanceUni
                    .convert()
                    .with(UniReactorConverters.toMono());

        } catch(Exception ex) {
            log.error("There is an error: {}", ex.getMessage());
            return null;
        }
    }
}

WAY 1: REACTIVE WAY

Util:


@Slf4j
@Component
public class JWTUtil {

    @Autowired
    private MyModelRepository myModelRepository;

    @Value("${springbootwebfluxjjwt.jjwt.secret}")
    private String secret;

    @Value("${springbootwebfluxjjwt.jjwt.expiration}")
    private String expirationTime;

    private Key key;


    @PostConstruct
    public void init(){
        this.key = Keys.hmacShaKeyFor(secret.getBytes());
    }


    public Claims getAllClaimsFromToken(String token) {
        // Here get token claims
    }


    public String getUsernameFromToken(String token) {
        // Here get token username
    }


    public Date getExpirationDateFromToken(String token) {
        // Here get expiration date from token;
    }


    private Boolean isTokenExpired(String token) {
        // Here validates token
    }


    public String generateToken (User user) {

        ArrayList<Long> usr_id = new ArrayList<Long>(Collections.singleton(user.getId()));
        ArrayList<String> usr_name = new ArrayList<String>(Collections.singleton(user.getName()));
        ArrayList<String> usr_lastname = new ArrayList<>(Collections.singleton(user.getLastName()));

        Map<String, Object> claims = new HashMap<>();
        claims.put("usr_id", usu_id);
        claims.put("usr_name", usu_name);
        claims.put("usr_lastname", usu_lastname);

        String[] desiredValueFromInside= {null};

        Disposable myInstance= myModelRepository.getMyModelInstance()
                .log()
                .subscribeOn(Schedulers.parallel())
                .subscribe(
                    // On next
                    instance-> {
                        desiredValueFromInside[0] = instance.getDesiredField();
                        claims.put("desiredField", instance.getDesiredField());

                        log.info("SUBSCRIBING, instance.getDesiredField() IS: {}", impuesto.getImp_tasa());
                        log.info("SUBSCRIBING, THE VAR desiredValueFromInsideIS: {}", impuestoFromInside[0]);
                    },
                    // On error
                    error -> log.error("THERE S A PROBLEM: {}", error),
                    // On complete
                    () -> log.info("WE ARE DONE!")
                );

        log.info("DEBUGGING, FROM OUTSIDE desiredValueFromInside IS: {}", desiredValueFromInside[0]);
        log.info("DEBUGGING, FROM OUTSIDE claims.get(\"desiredField\") IS: {}", claims.get("desiredField"));

        return doGenerateToken(claims, user.getUsername());
    }


    private String doGenerateToken(Map<String, Object> claims, String username) {
        // Here generate token
    }


    public Boolean validateToken(String token) {
        // Here validates token
    }
}

Console:

.
.
.
2022-03-02 11:21:02.485  INFO 44752 --- [ntloop-thread-0] .r.p.i.DefaultSqlClientPoolConfiguration : HR000025: Connection pool size: 100
2022-03-02 11:21:02.500  INFO 44752 --- [ntloop-thread-0] c.a.o.configuration.security.JWTUtil     : DEBUGGING, FROM OUTSIDE desiredValueFromInside IS: null
2022-03-02 11:21:02.500  INFO 44752 --- [ntloop-thread-0] c.a.o.configuration.security.JWTUtil     : DEBUGGING, FROM OUTSIDE claims.get(\"desiredField\") IS: null
2022-03-02 11:21:02.500  INFO 44752 --- [     parallel-2] reactor.Mono.FromPublisher.1             : onSubscribe(MonoNext.NextSubscriber)
2022-03-02 11:21:02.507  INFO 44752 --- [     parallel-2] reactor.Mono.FromPublisher.1             : request(unbounded)
.
.
.
2022-03-02 11:21:03.834  INFO 44752 --- [ntloop-thread-1] reactor.Mono.FromPublisher.1             : onNext(MyModel(id=1, desired_field="I am the desired value")
2022-03-02 11:21:03.849  INFO 44752 --- [ntloop-thread-1] c.a.o.configuration.security.JWTUtil     : SUBSCRIBING, instance.getDesiredField() IS: "I am the desired value"
2022-03-02 11:21:03.849  INFO 44752 --- [ntloop-thread-1] c.a.o.configuration.security.JWTUtil     : SUBSCRIBING, THE VAR desiredValueFromInsideIS: "I am the desired value"
2022-03-02 11:21:03.849  INFO 44752 --- [ntloop-thread-1] c.a.o.configuration.security.JWTUtil     : "WE ARE DONE!"
2022-03-02 11:21:03.849  INFO 44752 --- [ntloop-thread-1] reactor.Mono.FromPublisher.1             : onComplete()

WAY 2: BLOCKING WAY

Util:


@Slf4j
@Component
public class JWTUtil {

    @Autowired
    private MyModelRepository myModelRepository;

    @Value("${springbootwebfluxjjwt.jjwt.secret}")
    private String secret;

    @Value("${springbootwebfluxjjwt.jjwt.expiration}")
    private String expirationTime;

    private Key key;


    @PostConstruct
    public void init(){
        this.key = Keys.hmacShaKeyFor(secret.getBytes());
    }


    public Claims getAllClaimsFromToken(String token) {
        // Here get token claims
    }


    public String getUsernameFromToken(String token) {
        // Here get token username
    }


    public Date getExpirationDateFromToken(String token) {
        // Here get expiration date from token;
    }


    private Boolean isTokenExpired(String token) {
        // Here validates token
    }


    public String generateToken (User user) {

        ArrayList<Long> usr_id = new ArrayList<Long>(Collections.singleton(user.getId()));
        ArrayList<String> usr_name = new ArrayList<String>(Collections.singleton(user.getName()));
        ArrayList<String> usr_lastname = new ArrayList<>(Collections.singleton(user.getLastName()));

        Map<String, Object> claims = new HashMap<>();
        claims.put("usr_id", usu_id);
        claims.put("usr_name", usu_name);
        claims.put("usr_lastname", usu_lastname);

        String[] desiredValueFromInside= {null};

        String desiredField = myModelRepository.getMyModelInstance()
                .map(MyModel::getDesiredField)
                .share()
                .block();

        desiredValueFromInside[0] = instance.getDesiredField();
        claims.put("desiredField", instance.getDesiredField());

        log.info("DEBUGGING, FROM OUTSIDE desiredValueFromInside IS: {}", desiredValueFromInside[0]);
        log.info("DEBUGGING, FROM OUTSIDE claims.get(\"desiredField\") IS: {}", claims.get("desiredField"));

        return doGenerateToken(claims, user.getUsername());
    }


    private String doGenerateToken(Map<String, Object> claims, String username) {
        // Here generate token
    }


    public Boolean validateToken(String token) {
        // Here validates token
    }
}

Console: There are and infinite loop of this console log.

.
.
.
2022-03-02 11:40:58.640  WARN 38436 --- [-thread-checker] io.vertx.core.impl.BlockedThreadChecker  : Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 9212 ms, time limit is 2000 ms

io.vertx.core.VertxException: Thread blocked
    at [email protected]/java.lang.Thread.sleep(Native Method) ~[na:na]
    at app//reactor.core.publisher.NextProcessor.block(NextProcessor.java:135) ~[reactor-core-3.4.14.jar:3.4.14]
    at app//reactor.core.publisher.MonoProcessor.block(MonoProcessor.java:116) ~[reactor-core-3.4.14.jar:3.4.14]
.
.
.

CodePudding user response:

The main point here i think is the way you are organizing the code, it seems to me that you are using a reactive framework, but thinking and coding in an imperative way.

If you change the generateToken and doGenerateToken to return a Mono, you can do something like the following:

    private Mono<String> generateToken(User user) {
        ArrayList<Long> usr_id = new ArrayList<Long>(Collections.singleton(user.getId()));
        ArrayList<String> usr_name = new ArrayList<String>(Collections.singleton(user.getName()));
        ArrayList<String> usr_lastname = new ArrayList<>(Collections.singleton(user.getLastName()));

        return myModelRepository.getMyModelInstance()
                .map(modelInstance -> {
                    Map<String, Object> claims = new HashMap<>();

                    //put all values you need in claims map...

                    //update doGenerateToken code to return a Mono<String>
                    return doGenerateToken(claims, user.getUsername());
                });
    }
  • Related