The problems with binding RSocket and gRPC in spring boot - spring-boot

everyone!
I got some idea to use gRPC protobuff code generation implementation as a data layer API to use it instead of POJO in RSocket protocol.
Here is the implementation:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
option java_package = "me.some.protoapi";
option java_multiple_files = true;
message ValidationTaskRequest {
int64 id = 1;
string name = 2;
}
message ValidationTaskResponse {
int64 id = 1;
ValidationStatus status = 2;
ValidationError error = 3;
}
message ValidationError {
string reason = 1;
}
enum ValidationStatus {
PASSED = 0;
DECLINED = 1;
}
RSocket configuration
#Configuration
public class RSocketConfiguration {
#Bean
public RSocket rSocket(#Value("${rsocket.client.port}") int port) {
return RSocketFactory
.connect()
.mimeType(MimeTypeUtils.ALL_VALUE, MimeTypeUtils.ALL_VALUE)
.frameDecoder(PayloadDecoder.ZERO_COPY)
.transport(TcpClientTransport.create(port))
.start()
.retry()
.block();
}
#Bean
public RSocketRequester rSocketRequester(RSocket rSocket, RSocketStrategies rSocketStrategies) {
return RSocketRequester.wrap(
rSocket,
MimeTypeUtils.ALL,
MimeTypeUtils.ALL,
rSocketStrategies
);
}
}
and the service itself
#Service
public class ValidationServiceImpl implements ValidationService {
private final Logger logger = LoggerFactory.getLogger(ValidationServiceImpl.class);
private final TaskService taskService;
private final ReactiveRedisTemplate<String, Task> redis;
private final RSocketRequester rSocketRequester;
private final RedisTopicHelper redisHelper;
#Value("${rsocket.routes.validation}")
private String rSocketValidationRoute;
#Value("${validation.interval}")
private Optional<Integer> validationInterval;
public ValidationServiceImpl(TaskService taskService, ReactiveRedisTemplate<String, Task> redis, RSocketRequester rSocketRequester, RedisTopicHelper redisHelper) {
this.taskService = taskService;
this.redis = redis;
this.rSocketRequester = rSocketRequester;
this.redisHelper = redisHelper;
}
#Override
public void startValidationProcess() {
logger.info("validation listener started");
Flux.interval(Duration.ofMillis(validationInterval.orElse(1000)))
.flatMap(i -> redis.keys(redisHelper.topicAllKeys()))
.flatMap(redis.opsForValue()::get)
.filter(it -> !it.isVerified())
.flatMap(this::requestValidation)
.log()
.metrics()
.subscribe(result -> {
if ( result.getError() != null ) {
logger.error(result.getError().getReason());
} else {
taskService.markTaskAsVerified(result.getId(), result.getStatus());
redis.opsForValue().delete(redisHelper.specifiedTopicWithId(result.getId()));
}
});
}
private Mono<ValidationTaskResponse> requestValidation(Task task) {
return rSocketRequester
.route(rSocketValidationRoute)
.data(
ValidationTaskRequest
.newBuilder()
.setId(task.getId())
.setName(task.getName())
)
.retrieveMono(ValidationTaskResponse.class);
}
}
But, when the spring boot service is starting I caught exception
java.lang.IllegalArgumentException: No decoder for me.some.protoapi.ValidationTaskResponse
at org.springframework.messaging.rsocket.RSocketStrategies.decoder(RSocketStrategies.java:92) ~[spring-messaging-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.messaging.rsocket.DefaultRSocketRequester$DefaultRequestSpec.retrieveMono(DefaultRSocketRequester.java:274) ~[spring-messaging-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.messaging.rsocket.DefaultRSocketRequester$DefaultRequestSpec.retrieveMono(DefaultRSocketRequester.java:258) ~[spring-messaging-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at me.some.test.api.services.validation.ValidationServiceImpl.requestValidation(ValidationServiceImpl.java:73) ~[main/:na]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:378) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:107) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmit(FluxFlatMap.java:530) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapInner.onNext(FluxFlatMap.java:972) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onNext(FluxUsingWhen.java:355) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:107) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:242) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.2.RELEASE.jar:3.3.2.RELEASE]
at io.lettuce.core.RedisPublisher$ImmediateSubscriber.onNext(RedisPublisher.java:888) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.RedisPublisher$RedisSubscription.onNext(RedisPublisher.java:281) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.RedisPublisher$SubscriptionCommand.complete(RedisPublisher.java:756) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.45.Final.jar:4.1.45.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.45.Final.jar:4.1.45.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.45.Final.jar:4.1.45.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.45.Final.jar:4.1.45.Final]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
As you can see, the problem is in decoding proto-models in RSocket transport layer. I know, that gRPC its a binary serializing and it has some magic with binary description below. Has anyone tried to bing this two technologies? Any ideas would be very helpful. Thanks.

I have never faced this problem, but I've configurated my receiver and requester for an JSON payload.
Could you try to set an octet stream mime type for example?
#Bean
RSocket rSocket() {
return RSocketFactory
.connect()
.metadataMimeType("message/x.rsocket.composite-metadata.v0")
.frameDecoder(PayloadDecoder.ZERO_COPY)
.dataMimeType(MimeTypeUtils.APPLICATION_JSON_VALUE)
.transport(TcpClientTransport.create(ztlServerHostname, port))
.start()
.block();
}
#Bean
RSocketRequester rSocketRequester(RSocket rSocket, RSocketStrategies rSocketStrategies) {
return RSocketRequester.wrap(rSocket, MimeTypeUtils.APPLICATION_JSON,
MimeTypeUtils.parseMimeType("message/x.rsocket.composite-metadata.v0"),
rSocketStrategies);
}
The full code is here

May be a ProtobufDecoder() can help you when registered in rsocketStrategies
rsocketRequesterBuilder
.rsocketStrategies(b -> b.decoder(new ProtobufDecoder()))
.connectTcp("localhost",7000)
.block();

Related

Issue handling #MultipartForm MultipartFormDataInput contentImage, #PathParam and #FormParam in the same endpoint method

I'm implementing the following approach to handle a file in two methods of my REST web service endpoint based on GraalVM, Quarkus Framework, MAVEN, the first method "processImage" works pretty good, but the second "createOrUpdateCertificate" doesn't work as expected, while trying to DEBUG a test using POSTMAN as follows;
it delivers the following exception:
2022-05-02 09:35:00,812 DEBUG [org.jbo.res.res.i18n] (executor-thread-0) RESTEASY002305: Failed executing POST /api/certificates/certificate/aou/2: javax.ws.rs.BadRequestException: RESTEASY003320: Failed processing arguments of publ
ic javax.ws.rs.core.Response com.lumston.api.CertificateResource.createOrUpdateCertificate(java.lang.Long,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Double,java.lang.Double,java.lang.Byte,org.jboss
.resteasy.plugins.providers.multipart.MultipartFormDataInput,java.lang.Long,java.lang.Long,java.lang.String,java.lang.String,org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput)
at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:120)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:128)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:408)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:69)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:91)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:545)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.IllegalArgumentException: RESTEASY003750: Request media type is not application/x-www-form-urlencoded
at org.jboss.resteasy.plugins.server.BaseHttpRequest.getFormParameters(BaseHttpRequest.java:66)
at org.jboss.resteasy.plugins.server.BaseHttpRequest.getDecodedFormParameters(BaseHttpRequest.java:74)
at org.jboss.resteasy.core.FormParamInjector.inject(FormParamInjector.java:34)
at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:95)
... 25 more
I have been reading the documentation, but I don't find what's the exact issue with the approach, I have also been trying a couple of attempts that I have summited as commented lines below.
#Path("/api/certificates")
public class CertificateResource implements LoggerMessageable {
private static final Logger logger = Logger.getLogger(CertificateResource.class.getName());
private static final Integer UNEXPECTED_CODE_ERROR = -1;
#Inject
CertificateRepository repo;
private CertificateEntity certificate;
private Response response;
#POST
#Path("/image/moderation")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public Response processImage(#MultipartForm MultipartFormDataInput multipartFormDataInput) {
File promotionalImage = ImageFileService.convertToFile(multipartFormDataInput);
String fileName = promotionalImage.getName();
//... handling response
return response;
}
#POST
#Path("/certificate/aou/{id}")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Response createOrUpdateCertificate(
#PathParam(value = "id") Long id,
#FormParam(value = "title") String title,
#FormParam(value = "description") String description,
#FormParam(value = "validAt") String validAt,
#FormParam(value = "validUntil") String validUntil,
#FormParam(value = "value") Double value,
#FormParam(value = "amount") Double amount,
#FormParam(value = "status") Byte status,
MultipartFormDataInput contentImage,
// #MultipartForm MultipartFormDataInput contentImage,
// #FormParam(value = "contentImage") MultipartFormDataInput contentImage,
#FormParam(value = "commerceId") Long commerceId,
#FormParam(value = "stateId") Long stateId,
#FormParam(value = "termsAndConditions") String termsAndConditions,
#FormParam(value = "restrictions") String restrictions,
MultipartFormDataInput horizontalContentImage
// #MultipartForm MultipartFormDataInput horizontalContentImage
// #FormParam(value = "horizontalContentImage") MultipartFormDataInput horizontalContentImage
) {
logger.info("starts createOrUpdateCertificate");
try {
certificate = new CertificateEntity();
certificate.setId(id);
certificate.setTitle(title);
certificate.setDescription(description);
certificate.setValidAt(validAt);
certificate.setValidUntil(validUntil);
certificate.setValue(value);
certificate.setAmount(amount);
certificate.setStatus(status);
byte[] imageInBytes = ImageFileService.convertToBytes(contentImage);
Clob clobImage = ImageFileService.serializeByteArrayImageIntoClob(imageInBytes);
certificate.setImage(clobImage);
certificate.setCommerceId(commerceId);
certificate.setStateId(stateId);
certificate.setTermsAndConditions(termsAndConditions);
certificate.setRestrictions(restrictions);
byte[] horizontalImageInBytes = ImageFileService.convertToBytes(horizontalContentImage);
Clob clobHorizontalImage = ImageFileService.serializeByteArrayImageIntoClob(horizontalImageInBytes);
certificate.setHorizontalImage(clobHorizontalImage);
if (id != null) {
// update
certificate.setUpdatedAt(DateService.generateAndFormatDateTime());
logger.info("updating a certificate");
response = Response.ok(certificate).build();
} else {
// persist if id==null
certificate.setCreatedAt(DateService.generateAndFormatDateTime());
logger.info("persisting a new certificate");
response = Response.status(Response.Status.CREATED).build();
}
repo.persist(certificate);
logger.info("ends createOrUpdateCertificate");
} catch (Exception e) {
LocalDateTime localDateTime = LocalDateTime.now();
loggerMessageConfiguration(e, localDateTime);
CertificateRESTException certificateRESTException = new CertificateRESTException(ExceptionInfoMessageHandler.generateExceptionInfo(localDateTime, e));
response = certificateRESTException.handlingException(certificateRESTException);
}
return response;
}
}
I hope and appreciate if somebody can help me telling me how to solve this issue.

How to only allow specific fields to sort by in a Spring Data JPA Repository Pageable?

Using a Pageable parameter in a Spring Data JPA Repository allows for specifying fields to sort by like: PageRequest.of(0, 50, Sort.by("field1", "field2")), which would sort by field1 and field2 ascending.
It works by appending an ORDER BY clause directly by doing SQL injection which would result in a JPA query like SELECT a FROM SomeEntity a ORDER BY field1, field2. However, if a non-existing field name is passed in it would result in a org.springframework.dao.InvalidDataAccessApiUsageException as seen below.
How do you whitelist, only allow specific fields, or validate the sorting without adding boilerplate code in a service that wraps the repository? Same goes for in a #RestController ensuring that a 400 level HttpStatus.BAD_REQUEST is returned to the API?
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:531)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:154)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:149)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy340.searchPaged(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.zeroturnaround.jrebel.integration.springdata.RepositoryReloadingProxyFactoryBuilder$ReloadingMethodHandler.invoke(RepositoryReloadingProxyFactoryBuilder.java:80)
...
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:725)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:113)
at jdk.internal.reflect.GeneratedMethodAccessor749.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
at com.sun.proxy.$Proxy265.createQuery(Unknown Source)
at jdk.internal.reflect.GeneratedMethodAccessor749.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
at com.sun.proxy.$Proxy265.createQuery(Unknown Source)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.createJpaQuery(AbstractStringBasedJpaQuery.java:150)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateQuery(AbstractStringBasedJpaQuery.java:86)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:226)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:175)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 127 more
Caused by: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity [SELECT a FROM com.example.SomeEntity a order by a.field1 asc, a.field2 asc]
at org.hibernate.QueryException.generateQueryException(QueryException.java:120)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:604)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:716)
... 154 more
Caused by: org.hibernate.QueryException: could not resolve property: field1 of: com.example.SomeEntity
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:77)
at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:71)
at org.hibernate.persister.entity.AbstractEntityPersister.toType(AbstractEntityPersister.java:2043)
at org.hibernate.hql.internal.ast.tree.FromElementType.getPropertyType(FromElementType.java:412)
at org.hibernate.hql.internal.ast.tree.FromElement.getPropertyType(FromElement.java:520)
at org.hibernate.hql.internal.ast.tree.DotNode.getDataType(DotNode.java:694)
at org.hibernate.hql.internal.ast.tree.DotNode.prepareLhs(DotNode.java:269)
at org.hibernate.hql.internal.ast.tree.DotNode.resolve(DotNode.java:209)
at org.hibernate.hql.internal.ast.HqlSqlWalker.resolve(HqlSqlWalker.java:1053)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.expr(HqlSqlBaseWalker.java:1303)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderExpr(HqlSqlBaseWalker.java:1887)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderExprs(HqlSqlBaseWalker.java:1681)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.orderClause(HqlSqlBaseWalker.java:1654)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:666)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:325)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:273)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
... 160 more
I ended up using JSR-303 validations on the repository methods to whitelist the sort fields.
Enable method validation post processor to run JSR-303 validation annotations at the method level.
ValidationConfig.java
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Create a validation that takes in a list of sort fields to validate against.
AllowSortFields.java
#Documented
#Constraint(validatedBy = {AllowSortFieldsValidator.class})
#Target({ANNOTATION_TYPE, TYPE, FIELD, PARAMETER})
#Retention(RUNTIME)
public #interface AllowSortFields {
String message() default "Sort field values provided are not within the allowed fields that are sortable.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Specify an array of fields that are allowed.
*
* #return the allowed sort fields
*/
String[] value() default {};
}
AllowSortFieldsValidator.java
/**
* Validates a list of sort fields within a Pageable against an allowed list.
*/
public class AllowSortFieldsValidator implements ConstraintValidator<AllowSortFields, Pageable> {
private List<String> allowedSortFields;
static final String PROPERTY_NOT_FOUND_MESSAGE = "The following sort fields [%s] are not within the allowed fields. "
+ "Allowed sort fields are: [%s]";
#Override
public void initialize(AllowSortFields constraintAnnotation) {
allowedSortFields = Arrays.asList(constraintAnnotation.value());
}
#Override
public boolean isValid(Pageable value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (CollectionUtils.isEmpty(allowedSortFields)) {
return true;
}
// ignore unsorted
Sort sort = value.getSort();
if (sort.isUnsorted()) {
return true;
}
String fieldsNotFound = fieldsNotFoundAsCommaDelimited(sort);
// all found fields are allowed
if (StringUtils.isEmpty(fieldsNotFound)) {
return true;
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(String.format(PROPERTY_NOT_FOUND_MESSAGE, fieldsNotFound, String.join(",", allowedSortFields)))
.addConstraintViolation();
return false;
}
private String fieldsNotFoundAsCommaDelimited(Sort sort) {
String fieldsNotFound = sort.stream()
.map(order -> order.getProperty())
.filter(property -> !allowedSortFields.contains(property))
.collect(joining(","));
return fieldsNotFound;
}
}
AllowSortFieldsValidatorSmallTest.java
public class AllowSortFieldsValidatorSmallTest {
private static final String[] ALLOWED_SORT_FIELDS = new String[]{"allowed1", "allowed2"};
private static final String ALLOWED_SORT_FIELDS_DELIMITED = String.join(",", Arrays.asList(ALLOWED_SORT_FIELDS));
private static Validator validator;
#BeforeClass
public static void setupValidator() throws Exception {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void isValid_TwoOfFourFieldsAllowed_FalseWithExpectedMessageExplainingDisallowedFields() {
List<String> sortFields = List.of("allowed1", "allowed2|desc", "notfound1", "not.found2");
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
String expected = String.format(AllowSortFieldsValidator.PROPERTY_NOT_FOUND_MESSAGE, "notfound1,not.found2", ALLOWED_SORT_FIELDS_DELIMITED);
String actual = getConstraintMessages(constraintViolations);
assertEquals(expected, actual);
}
#Test
public void isValid_NoSortFields_True() {
List<String> sortFields = null;
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_EmptyAllowedSortFields_True() {
List<String> sortFields = List.of("allowed1", "allowed2|desc", "notfound1", "not.found2");
EmptyAllowedSortFields toValidate = newEmptyAllowedSortFields(sortFields);
Set<ConstraintViolation<EmptyAllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_AllSortFieldsFoundAsAllowed_True() {
List<String> sortFields = Arrays.asList(ALLOWED_SORT_FIELDS);
AllowedSortFields toValidate = newAllowedSortFields(sortFields);
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
#Test
public void isValid_NullValue_True() {
AllowedSortFields toValidate = new AllowedSortFields();
toValidate.pageable = null;
Set<ConstraintViolation<AllowedSortFields>> constraintViolations = validator.validate(toValidate, Default.class);
assertTrue(constraintViolations.isEmpty());
}
private String getConstraintMessages(Set<ConstraintViolation<AllowedSortFields>> constraintViolations) {
String actual = constraintViolations.stream()
.map(c -> c.getMessage())
.collect(joining(","));
return actual;
}
private AllowedSortFields newAllowedSortFields(List<String> sortFields) {
AllowedSortFields toValidate = new AllowedSortFields();
toValidate.pageable = new CustomPageable().sort(sortFields);
return toValidate;
}
private EmptyAllowedSortFields newEmptyAllowedSortFields(List<String> sortFields) {
EmptyAllowedSortFields toValidate = new EmptyAllowedSortFields();
toValidate.pageable = new CustomPageable().sort(sortFields);
return toValidate;
}
public class AllowedSortFields {
#AllowSortFields({"allowed1", "allowed2"})
public Pageable pageable;
}
public class EmptyAllowedSortFields {
#AllowSortFields
public Pageable pageable;
}
}
Finally the usage within the repository. Be sure to put #Validated at the top of the class.
ExampleSearchRepository.java
public interface ExampleSearchRepository extends JpaRepository<ExampleSearch, Integer>,
JpaSpecificationExecutor<ExampleSearch>, PagingAndSortingRepository<ExampleSearch, Integer> {
public Page<ExampleSearch> search(
#Param("searchCriteria") ExampleSearchCriteria searchCriteria,
#AllowSortFields({"field1","subfield.name"}) Pageable pageable);

register webhook tg bot using spring boot

I am using telegrambots-spring-boot-starter v 5.2.0 and trying register my bot
Here's my bot config:
bot.url=https://74e437885ee9.ngrok.io
bot.path=adam
#Slf4j
#Configuration
public class BotConfig {
#Value("${bot.url}")
private String BOT_URL;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url(BOT_URL).build();
}
// Create it as
#Bean
public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {
AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
// DefaultWebhook defaultWebhook = new DefaultWebhook();
// defaultWebhook.setInternalUrl(BOT_URL);
// defaultWebhook.registerWebhook(adamSmithBot);
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
log.info("SetWebHook from AdamSmith bot {}", adamSmithBot.getSetWebhook());
telegramBotsApi.registerBot(adamSmithBot, adamSmithBot.getSetWebhook());
return adamSmithBot;
}
}
But it dont working, but when i send this request, it working perfectly and updates recieve to me
https://api.telegram.org/MY_TOKEN_HERE/setWebhook?url=https://74e437885ee9.ngrok.io
I think my mistake in BotConfig,but i also publush my other clases bot and controller:
public class AdamSmithBot extends SpringWebhookBot {
#Value("${bot.token}")
private String TOKEN;
#Value("${bot.name}")
private String BOT_USERNAME;
#Value("${bot.path}")
private String BOT_PATH;
public AdamSmithBot(SetWebhook setWebhook) {
super(setWebhook);
}
public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
super(options, setWebhook);
}
#Override
public String getBotUsername() {
return BOT_USERNAME;
}
#Override
public String getBotToken() {
return TOKEN;
}
#Override
public BotApiMethod<?> onWebhookUpdateReceived(Update update) {
if (update.getMessage() != null && update.getMessage().hasText()) {
Long chatId = update.getMessage().getChatId();
try {
execute(new SendMessage(chatId.toString(), "HI HANDSOME " + update.getMessage().getText()));
} catch (TelegramApiException e) {
throw new IllegalStateException(e);
}
}
return null;
}
#Override
public String getBotPath() {
return "adam";
}
}
Controller:
#Slf4j
#RestController
public class WebHookBotRecieveController {
#Autowired
private AdamSmithBot adamSmithBot;
#PostMapping("/")
public void getUpdate(#RequestBody Update update){
log.info("some update recieved {}",update.toString());
adamSmithBot.onWebhookUpdateReceived(update);
}
#PostMapping("/callback/adam")
public void getUpdateWithDifferentUrl(#RequestBody Update update){
log.info("some update recieved {}",update.toString());
adamSmithBot.onWebhookUpdateReceived(update);
}
}
NOTE: I seemd some info here:
https://github.com/rubenlagus/TelegramBots/wiki/How-To-Update
They do that:
https://i.stack.imgur.com/9JKRT.png
But when i trying put DefaultWebhook class instead it produce NullPointerException
What i made wrong?
EDIT : I refactored some code
#Value("${bot.url}") private String BOT_URL; - where was null value (fixed),reloaded library, but now i have that exception:
Caused by: javax.ws.rs.ProcessingException: Failed to start Grizzly HTTP server: Cannot assign requested address: bind
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:270) ~[jersey-container-grizzly2-http-2.33.jar:na]
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:93) ~[jersey-container-grizzly2-http-2.33.jar:na]
at org.telegram.telegrambots.updatesreceivers.DefaultWebhook.startServer(DefaultWebhook.java:64) ~[telegrambots-5.2.0.jar:na]
at org.telegram.telegrambots.meta.TelegramBotsApi.<init>(TelegramBotsApi.java:50) ~[telegrambots-meta-5.2.0.jar:na]
at ru.website.selenium.bot.telegram.config.BotConfig.adamSmithBot(BotConfig.java:44) ~[classes/:na]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.CGLIB$adamSmithBot$0(<generated>) ~[classes/:na]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d$$FastClassBySpringCGLIB$$1e185cfd.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.8.jar:5.3.8]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.8.jar:5.3.8]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.adamSmithBot(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.8.jar:5.3.8]
... 39 common frames omitted
Caused by: java.net.BindException: Cannot assign requested address: bind
at java.base/sun.nio.ch.Net.bind0(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.bind(Net.java:461) ~[na:na]
at java.base/sun.nio.ch.Net.bind(Net.java:453) ~[na:na]
at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227) ~[na:na]
at java.base/sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:80) ~[na:na]
at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bindToChannelAndAddress(TCPNIOBindingHandler.java:107) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bind(TCPNIOBindingHandler.java:64) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:215) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:195) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:186) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.http.server.NetworkListener.start(NetworkListener.java:711) ~[grizzly-http-server-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.http.server.HttpServer.start(HttpServer.java:256) ~[grizzly-http-server-2.4.4.jar:2.4.4]
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:267) ~[jersey-container-grizzly2-http-2.33.jar:na]
... 53 common frames omitted
Process finished with exit code 0
Okay, I get it, I didn't know that TelegramBotApi runs a grizzly server underneath and that was the reason for my mistakes
For those who ever come here with the same problem, I will describe the solution :
Let's start with the fact that for the test on localhost we need a public address , e.g. ngrok , then we have to do the following, start grizzli on , let's say port 80 (localhost), specify the correct ngrok url , and then run spring boot application on , let's say port 8080, examples in the code
For ex. ngrok forwarding:
Forwarding http://b44ecce72666.ngrok.io -> http://localhost:8080
Forwarding https://b44ecce72666.ngrok.io -> http://localhost:8080
#Slf4j
#Configuration
public class BotConfig {
// #Value("${bot.url}")
// #NotNull
// #NotEmpty
// private String BOT_URL;
#Value("${bot.token}")
#NotNull
#NotEmpty
private String TOKEN;
#Value("${bot.name}")
#NotNull
#NotEmpty
private String BOT_USERNAME;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url("https://b44ecce72666.ngrok.io").build();
} // public address, now it is ngrok, in the future it will (i think) be the server address
// Create it as
#Bean
public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {
AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
adamSmithBot.setBOT_USERNAME(BOT_USERNAME);
adamSmithBot.setTOKEN(TOKEN);
adamSmithBot.setBOT_PATH("adam");
DefaultWebhook defaultWebhook = new DefaultWebhook();
defaultWebhook.setInternalUrl(
"http://localhost:80"); // the port to start the server, on the localhost computer, on the server it
// be the server address
// defaultWebhook.registerWebhook(adamSmithBot);
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);
log.info("SetWebHook from AdamSmith bot {}", setWebhookInstance);
adamSmithBot.getBotUsername();
telegramBotsApi.registerBot(adamSmithBot, setWebhookInstance);
return adamSmithBot;
}
}
Next is code of webhook bot:
#Slf4j
#Setter
public class AdamSmithBot extends SpringWebhookBot {
#Value("${bot.token}")
private String TOKEN;
#Value("${bot.name}")
private String BOT_USERNAME;
#Value("${bot.path}")
private String BOT_PATH;
#Autowired
private MainService mainService;
public AdamSmithBot(SetWebhook setWebhook) {
super(setWebhook);
}
public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
super(options, setWebhook);
}
#Override
public String getBotUsername() {
log.info("BOT_USERNAME FROM ADAM BOT {}",BOT_USERNAME);
return BOT_USERNAME;
}
#Override
public String getBotToken() {
return TOKEN;
}
#Override
public BotApiMethod<?> onWebhookUpdateReceived(Update update) { //All messages coming from the grizzly server will trigger this method
log.info("Message teext {}",update.toString());
if (update.getMessage() != null && update.getMessage().hasText()) {
Long chatId = update.getMessage().getChatId();
List<PartialBotApiMethod<Message>> listOfCommands= mainService.receiveUpdate(update);
listOfCommands.forEach(x->{
try {
if(x instanceof SendMessage){
execute((SendMessage)x);
}
if(x instanceof SendPhoto){
execute((SendPhoto) x);
}
} catch (TelegramApiException e) {
e.printStackTrace();
}
});
}
return null;
}
#Override
public String getBotPath() {
return "adam"; //any other path here
}
}
My code is excessive in some places, and too fancy, but it's not hard to figure out. If you have any suggestions on how to make it better, I'd love to hear them.
This was the most helpful question and answer from #handsome so decided to post here some more additional info that was not so evident for me at the beginning.
#Configuration
public class BotConfig {
#Value("${telegram.webhook-host}")
private String webhookHost;
#Value("${telegram.endpoint}")
private String botEndpoint;
#Value("${telegram.token}")
private String botToken;
#Value("${telegram.username}")
private String botUsername;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url(webhookHost).build();
}
#Bean
public ParserBot getBot(SetWebhook setWebhook) {
ParserBot parserBot = new ParserBot(setWebhook);
parserBot.setBotPath(botEndpoint);
parserBot.setBotToken(botToken);
parserBot.setBotUsername(botUsername);
try {
DefaultWebhook defaultWebhook = new DefaultWebhook();
defaultWebhook.setInternalUrl("http://localhost:80");
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);
telegramBotsApi.registerBot(parserBot, parserBot.getSetWebhook());
} catch (TelegramApiException e) {
throw new RuntimeException("Can't register telegram bot", e);
}
return parserBot;
}
}
Most of the info is clear but sometimes it is necessary to say:
You can register webhook endpoint manually by calling https://api.telegram.org/bot<your_token>/setWebHook?url=<your_url>
In this case your URL can be any path you want. If you register your webhook endpoint using TelegramBotsApi, then your endpoint will look like https://host/callback/ , as example
telegram.webhook-host=https://12345124.eu.ngrok.io
telegram.endpoint="webhook"
then your endpoint is https://12345124.eu.ngrok.io/callback/webhook
Yes, you can't set any path to your endpoint but you don't need to set it manually
Another thing. To activate WebhookBot in application you have to use DefaultWebhook to activate TelegramBotsApi.useWebhook property. This is according to source.
Your endpoint have to be https. It can't be http.
Webhook can be set up only on ports 80, 88, 443 or 8443

ERR: Could not extract response: no suitable HttpMessageConverter

Im getting the above mentioned error:
Controller:
#Component
public class NiftyController {
#Autowired
private RestProxyTemplate restTemplate;
#Autowired
private NiftyDAO niftyDAO;
int count=1;
#Scheduled(fixedDelay=5000)
//#Scheduled(cron="*/5 * * * * ?")
public void loadNiftyData() throws URISyntaxException {
System.out.println("Method executed at every 5 seconds. Current time is : "+ new Date() );
System.out.println("Executed " +count++ +" Times");
URI uri = new URI("https://www.nseindia.com/live_market/dynaContent/live_watch/stock_watch/niftyStockWatch.json");
ResponseEntity<NiftyDTO> niftyResponse = restTemplate.getRestTemplate().exchange(uri, HttpMethod.GET, null, NiftyDTO.class);
NiftyDTO nifties = niftyResponse.getBody();
System.out.println(nifties.getTrdVolumesum());
//List<Nifty> saveNifty = niftyDAO.save(nifties);
//return (Nifty) nifties;
}
}
I want to fetch the data from the above link and save in the database
DTO Class
#JsonIgnoreProperties({"latestData","data"})
public class NiftyDTO {
private float trdVolumesumMil;
private String time;
private int declines;
private float trdValueSum;
private float trdValueSumMil;
private int unchanged;
private float trdVolumesum;
private int advances;
public float getTrdVolumesumMil() {
return trdVolumesumMil;
}
public void setTrdVolumesumMil(float trdVolumesumMil) {
this.trdVolumesumMil = trdVolumesumMil;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public int getDeclines() {
return declines;
}
public void setDeclines(int declines) {
this.declines = declines;
}
public float getTrdValueSum() {
return trdValueSum;
}
public void setTrdValueSum(float trdValueSum) {
this.trdValueSum = trdValueSum;
}
public float getTrdValueSumMil() {
return trdValueSumMil;
}
public void setTrdValueSumMil(float trdValueSumMil) {
this.trdValueSumMil = trdValueSumMil;
}
public int getUnchanged() {
return unchanged;
}
public void setUnchanged(int unchanged) {
this.unchanged = unchanged;
}
public float getTrdVolumesum() {
return trdVolumesum;
}
public void setTrdVolumesum(float trdVolumesum) {
this.trdVolumesum = trdVolumesum;
}
public int getAdvances() {
return advances;
}
public void setAdvances(int advances) {
this.advances = advances;
}
}
RestProxyTemplate:
#Component
public final class RestProxyTemplate {
RestTemplate restTemplate;
#PostConstruct
public void init(){
restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
InetSocketAddress address = new InetSocketAddress("MY PROXY GOES HERE",8080);
Proxy proxy = new Proxy(Proxy.Type.HTTP,address);
factory.setProxy(proxy);
restTemplate.setRequestFactory(factory);
}
public RestTemplate getRestTemplate() {
return restTemplate;
}
}
Error Log:
Method executed at every 5 seconds. Current time is : Mon Jun 05 10:45:38 IST 2017
Executed 1 Times
2017-06-05 10:45:38.141 INFO 108988 --- [ main]
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
2017-06-05 10:45:38.153 INFO 108988 --- [ main]
com.MyApplication.portfolio.PMSApplication : Started PMSApplication in 5.041 seconds (JVM running for 5.573)
2017-06-05 10:45:39.028 ERROR 108988 --- [pool-2-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.MyApplication.portfolio.controller.NiftyDTO] and content type [text/plain]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:835) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:819) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:599) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:572) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:493) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at com.MyApplication.portfolio.controller.NiftyController.loadNiftyData(NiftyController.java:44) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_92]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_92]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_92]
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) ~[spring-context-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-4.2.8.RELEASE.jar:4.2.8.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_92]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_92]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_92]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_92]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_92]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_92]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_92]
Im getting the No Sutiable HttpMessageConverter Found for the response type, can some one suggest the possible solution for this??
You need to process application/json rather than text/plain.
private static void temp() {
HttpHeaders requestHeaders = new HttpHeaders();
// requestHeaders.setAccept(Collections.singletonList(new MediaType("application","json")));
requestHeaders.setAccept(Collections.singletonList(new MediaType("text","plain")));
HttpEntity<?> requestEntity = new HttpEntity<>(requestHeaders);
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the Jackson message converter
MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter() ;
mc.setSupportedMediaTypes(Collections.singletonList(new MediaType("text","plain")));
restTemplate.getMessageConverters().add(mc);
String url = "https://www.nseindia.com/live_market/dynaContent/live_watch/stock_watch/niftyStockWatch.json";
ResponseEntity<NiftyDTO> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, NiftyDTO.class);
NiftyDTO res = responseEntity.getBody();
System.out.println(res);
}
Code got from the docs and modified a bit
UPDATE: After the change code works fine but there is another error Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type float from String "4,092.10": not a valid Float value
You will need to fix deserializing of data with , delimiter.

Exception while dispatching incoming RPC call - GWT 2.6

I'm working with a GWT(2.6) project together with Spring using "gwtrpc-spring" lib.
I'm trying to send a POJO from server to client, I get this POJO instance getting a Spring bean, and works well, but when I try to send it to client, I got this exception:
[WARN] Exception while dispatching incoming RPC call
com.google.gwt.user.client.rpc.SerializationException: Type 'java.lang.IllegalArgumentException' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable' and did not have a custom field serializer.For security purposes, this type will not be serialized.: instance = java.lang.IllegalArgumentException: Unable to find public method: setName with 0 params.
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:667)
at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:130)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:153)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:587)
at com.google.gwt.user.server.rpc.RPC.encodeResponse(RPC.java:605)
at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:393)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:545)
at org.gwtrpcspring.RemoteServiceDispatcher.invokeAndEncodeResponse(RemoteServiceDispatcher.java:100)
at org.gwtrpcspring.RemoteServiceDispatcher.processCall(RemoteServiceDispatcher.java:64)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:305)
at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:686)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:557)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:428)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:68)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:370)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489)
at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:960)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1021)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:865)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:668)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:745)
My POJO is:
import java.io.Serializable;
import com.google.gwt.user.client.rpc.IsSerializable;
public class ApplicationItem implements Serializable, IsSerializable
{
private static final long serialVersionUID = 1L;
private long id;
private String name;
private String path;
private String grant;
private String icon;
public ApplicationItem(){}
public void setId(long id)
{
this.id = id;
}
public long getId()
{
return id;
}
public void setName()
{
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public void setPath(String path)
{
this.path = path;
}
public String getPath()
{
return path;
}
public void setGrant(String grant)
{
this.grant = grant;
}
public String getGrant()
{
return grant;
}
public void setIcon(String icon)
{
this.icon = icon;
}
public String getIcon()
{
return icon;
}
#Override
public String toString()
{
return "ApplicationItem: ["+this.id+", "+this.name+", "+this.path+", "+this.grant+", "+this.icon+"]";
}
}
I really appreciate your help.

Resources